A Flexible Framework for Program Evolution and Verification
Olaf Owe, Jia-Chun Lin and Elahe Fazeldehkordi
Department of Informatics, University of Oslo, Norway
Keywords:
Program Evolution, Program Reasoning, Software Changes, Active Objects, Re-verification, Flexibility.
Abstract:
We propose a flexible framework for modeling of distributed systems, supporting evolution by means of un-
restricted modifications in such systems, and with support of verification and re-verification. We focus on the
setting of concurrent and object-oriented programs, and consider a core high-level modeling language sup-
porting active, concurrent objects. We show that our framework can deal with verification of software changes
that are not possible to verify in comparable frameworks. We demonstrate the approach by variations over a
simple example.
1 INTRODUCTION
Program development is in general a complicated pro-
cess where many kinds of mistakes can be made, in-
cluding bad design decisions, unclear specifications,
misunderstandings, and erroneous code or specifica-
tions. Problems made early may not be discovered
until much later. Redesigning or modifying code
made at an early stage in the software development
may affect many parts of the overall system. Making
changes in order to correct problematic decisions may
create new problems that are hard to foresee. These
kinds of problems are severe in the setting of con-
current programs when the interaction of the differ-
ent concurrent components is complicated, and also
in the setting of object-oriented programs, due to in-
heritance, late binding and code reuse.
We consider the setting of distributed, concurrent,
and object-oriented systems, and suggest a frame-
work for modeling, development, and evolution of
such systems with support of verification. Our
framework includes several life cycle aspects such
as (formal) requirement specification, design, exe-
cutable modeling, analysis, and maintenance. The
framework allows unrestricted changes in code and
requirements, and includes a theory for reverification
of a changed system. We include mechanisms for ef-
ficient, imperative style programming in a distributed
setting, including non-blocking as well as blocking
remote method calls, combined with suspension and
scheduling control of processes inside an object. Our
goal is flexibility, in the sense of support of unre-
stricted software changes and with simplicity of rever-
ification. We show that we can deal with software
changes that are not possible to verify in comparable
frameworks.
A framework that allows the simplest reverifica-
tion of any given software change, has the best flexi-
bility. Clearly incremental and modular reasoning are
preferable, as well as limiting the number of modules
to be affected by a given change. It is desirable to
avoid reverification of the whole system when pos-
sible. Flexibility depends on the choice of program-
ming and specification constructs, their semantics, as
well as the reasoning system. In particular flexibility
is affected by the choice of abstraction mechanisms.
For instance, for shared variable concurrency it is hard
to analyze the effect of software changes, even with an
advanced reasoning framework. And synchronization
by signaling is notoriously hard to reason about. In
the setting of behavioral subtyping, a change in a sub-
class may violate superclasses requirements, thereby
limiting flexibility.
Flexibility demands programming languages with
a compositional semantics and compositional reason-
ing frameworks. Compositional reasoning of classes
is supported by several approaches. Our framework
is based on a programming paradigm with composi-
tional semantics, cooperative scheduling to support
object-local synchronization control, using interface
abstraction to reduce dependencies between classes,
and the use of communication histories to enable
compositional specification and reasoning.
In the presence of class inheritance, modularity of
each subclass is advantageous, as cross-class depen-
dencies hinder flexibility. The strong dependencies of
behavioral subtyping can be reduced with the notion
of lazy behavioral subtyping (Dovland et al., 2010;
Owe, O., Lin, J. and Fazeldehkordi, E.
A Flexible Framework for Program Evolution and Verification.
DOI: 10.5220/0007690301770189
In Proceedings of the 7th International Conference on Model-Driven Engineering and Software Development (MODELSWARD 2019), pages 177-189
ISBN: 978-989-758-358-2
Copyright
c
2019 by SCITEPRESS – Science and Technology Publications, Lda. All rights reserved
177
Dovland et al., 2011); however, reasoning require-
ments to local calls in a superclass are imposed on
subclasses, which limits flexibility. A framework for
evolution based on this approach is given in (Dovland
et al., 2012).
We observe that changing a class C in the mid-
dle of a class hierarchy may in general affect existing
subclasses as well as superclasses. Clearly code in-
herited from C in subclasses could lead to inconsisten-
cies, since C is changed. And requirements imposed
on C from superclasses may also lead to inconsisten-
cies, something which may in general be remediated
by changes in these superclasses, thereby affecting
other subclasses of these superclasses as well. If a
class is changed, it is undesirable that its superclasses
also need to be modified, as this can destroy flexibil-
ity. This is the case in approaches where requirements
are pushed from superclasses to subclasses, as in the
case of behavioral subtyping. Therefore the seman-
tics of class inheritance matters, in particular when it
comes to inheritance of requirements.
In order to avoid this inherent flexibility limita-
tion, we build on an approach with separation of the
reuse of code from the reuse of specifications to al-
low unrestricted reuse of code and specifications. In
particular we build on the approach of behavioral in-
terface subtyping (Owe, 2015) where each class is
only required to satisfy its own interface specifica-
tions, and any invariant or other local specifications
given in the class. This means that a method rede-
fined in a subclass is allowed to break the require-
ments of the superclass which opens up for more
liberal modifications than work based on lazy behav-
ioral subtyping (Dovland et al., 2010; Dovland et al.,
2011), as no superclass requirements are imposed on a
subclass. This allows full control of the inheritance of
code and of requirements when a subclass is defined
and when it is modified, which is needed to avoid
inconsistent specifications due to inheritance. Thus
in our approach we can avoid inconsistencies due to
superclass requirements, simply by controlling which
requirements to inherit.
We allow unrestricted changes of code and spec-
ifications (assuming type correctness). This means
that one may write combinations of code and spec-
ifications that are inconsistent, for instance when a
class does not satisfy the requirements of its inter-
face(s). The framework will detect such inconsisten-
cies so that they may be resolved, by changing code
and/or specifications. In order to determine the conse-
quences of changes in a (super)class, the framework
needs to keep track of dependencies of local calls.
As mentioned, we show that our framework can
deal with software changes that are not possible to
verify in comparable frameworks. We also show how
to reason within a hierarchy where some classes are
verified and others not. We demonstrate our frame-
work by examples.
2 LANGUAGE SETTING
We consider the setting of asynchronously commu-
nicating objects, so-called active objects, supporting
blocking and non-blocking remote calls, but not re-
mote field access. In this setting, verification of a
system of concurrent objects can be done composi-
tionally, verifying each class separately, letting each
class and interface refer to its local history, reflecting
the time sequence of communication events such as
method calls and returns. Each class can be verified in
a sequential manner, and a compositional rule states
that a global invariant referring to the global history
can be obtained by conjunction of the local invari-
ants (on local histories) together with a wellformed-
ness predicate relating the local histories.
For the purpose of this paper, we consider a
core high-level imperative modeling language, given
in Figure 1, inspired by the concurrency model of
Creol (Johnsen and Owe, 2007). The language is exe-
cutable with an interpreter in Rewriting logic/Maude
(Clavel et al., 2008). A program consists of interfaces
and classes. A class may implement a number of
interfaces. Class instances represent concurrent and
active objects. Local data structures are defined by
(build-in or user-defined) data types. An interface can
extend other (super)interfaces and add declarations of
methods and invariants.
A class can inherit from a superclass while remov-
ing/adding/redefining method definitions, method
specifications and invariants. And fields w may be
added (an initial value r may be given, otherwise
the default value of the type is used). For simplic-
ity, we assume read-only access to method and class
parameters, and limit the discussion to single inher-
itance. When an interface extends another (su-
per)interface, all declarations and specifications are
inherited. When a class inherits another class
(the superclass), all code and specifications are inher-
ited unless redefined: A pre/post pair (P) is inherited
unless another is stated, an invariant (I) is inherited
unless another is stated, the initialization code (s) is
inherited unless another is stated, and a method body
(B) is inherited unless the method is redefined or re-
moved. Likewise, the implementation clause of the
superclass is inherited unless a new implementation
clause is provided, in which case the superclass im-
plementation clause is not inherited.
MODELSWARD 2019 - 7th International Conference on Model-Driven Engineering and Software Development
178
Pr ::= [In
Cl]
+
program
In ::= interface F[ extends F
+
]
?
{S
I
} interface declaration
Cl ::= class C [([T cp]
+
)]
?
[ implements F
+
]
?
class definition
[ inherits C(e)]
?
[ removing m
+
]
?
inheritance mechanisms
{[T w [:= r]
?
]
s
?
M
S
I
} class body
M ::= T m([T x]
) B P
method definition
S ::= T m([T x]
) P
method signature
B ::= {[[T x [:= r]
?
]
+
;]
?
[s;]
?
return r} method body
T ::= F | Any | Void | Bool | String | Int | Nat | ... types
v ::= x | w variables (local or field)
e ::= null | this | caller | v | cp | f (e) | (e) pure expressions
r ::= e | new C(e) | e.m(e) | C : m(e) right-hand-side/call/new
s ::= skip | [v :=]
?
r | s;s basic statements
| await v := e.m(e) | await e releasing statements
| if e then s [ else s]
?
fi if statement
P ::= [ [ A,A ] ]
+
[ where A
+
]
?
pre-/postconditions
I ::= inv A
+
[ where A
+
]
?
invariant specification
Figure 1: Language syntax. Specification elements are written in blue. F denotes an interface name, C a class name, m a
method name, cp a formal class parameter, w a field, x a method parameter or local variable. We use [ ] as meta parentheses
and superscipts , +, and ?, for repetition, non-empty repetition, and optional parts, respectively. Expressions e are side-
effect free, and e denotes a (possibly empty) expression list. Assertions A are first order Boolean expressions and may refer
to the local communication history h . A where clause defines auxiliary functions used for specification purposes. Other
statements, such as while loops, can be added.
An object variable must be typed by an interface, not
by a class. A variable declared of interface F is called
an F variable. Though type checking the language
guarantees that for an F variable, the object referred
to by the variable at run-time implements F. This is
called the interface substitution principle (Owe and
Ryl, 1999; Johnsen and Owe, 2007; Johnsen et al.,
2006). A remote call v := o.m(e) is type correct if the
interface of o supports a method m such that the type
of the actual parameters e is a subtype of the formal
parameters of m and the output type of m is a subtype
of v. For simplicity, we assume type correctness, and
assume that a class does not offer two declarations of
the same method name.
Dot-notation is reserved for late bound method
calls, i.e., for a C object o (an object o of run-time
class C) a remote call o.m(...) binds to the definition
of method m in C, if any, or the closest superclass
with a definition of m. This definition of m is denoted
C :m, and is referred to as m of C. The notation C : m
may also be used in program code, resulting in static
binding. Thus C :m binds to the method m of class C
if defined in C or the closest superclass. We assume
here that the static type checking will replace C :m by
B:m if the call binds to m of B.
We distinguish between public methods, those ex-
ported through an interface of the class, and private
methods, those that are not exported through any in-
terface of the class. Note that interface abstraction
defines the public-ness, rather than keywords such as
private and public. Thus a public method in a class
may be private in a subclass (and vice versa). Local
calls are possible with the syntax v := this.m(e), for
late bound calls, or v := C : m(e) for static calls. (We
let this have the enclosing class as its type.) Public
methods are required to maintain the class invariant.
Private methods may only be called locally, and may
not be used in non-blocking calls (since they may be
called in states violating the invariant).
Methods may be specified by pre/post specifica-
tions. This is needed for reasoning about local calls,
for which the invariant may be violated, and is in par-
ticular useful for private methods, and other locally
called methods. Multiple pre/post specifications of
each method are allowed, and a class may implement
multiple interfaces. A class without an implements
list will implement the empty interface Any, which is
the superinterface of all interfaces.
We allow “negative” inheritance by the syntax
removing m
1
,m
2
...., expressing that the listed
methods should not be inherited. By type checking it
must be ensured that public methods are not removed
and that the remaining methods in C (including inher-
ited ones) do not (directly or indirectly) lead to a call
on a removed method. The purpose of a removal is
to make a semantically simpler subclass, where irrel-
evant or problematic code is eliminated. In particular
this can be used to make verification easier, and even
avoid verification problems for instance when an in-
variant is redefined. (Removal of fields will mainly be
A Flexible Framework for Program Evolution and Verification
179
a typing issue.)
Apart from standard statements, we include both
static and dynamic calls, and we include cooperative
scheduling (with the await mechanism) allowing non-
blocking calls, something which is useful in a dis-
tributed concurrent setting.
2.1 History-based Specification
Histories are used to capture the time sequence of
communication events. Global histories capture all
communication events in a distributed system, and lo-
cal histories capture all communication events seen
from a given component. The local history h of a
component o is part of the global history H, and these
are related by the equation h = H/o where H/o de-
notes the projection of the global history H to all com-
munication events involving o as either the sender or
receiver.
The invariant of a class C may refer to fields, the
local history h, class parameters, and this. An in-
variant must be maintained by each public method of
the class (possibly inherited), and a class must satisfy
each implemented interface (using projection on the
history to reflect the subset of methods visible through
the interface). A method specification may in addition
refer to the formal parameters (including the caller)
and logical variables (primed variables), and a post-
condition may talk about the result (return). When
seen from another class with a larger alphabet, a C
invariant must hold on the alphabet of C.
The invariant of an interface F may refer to the
local history h and this, but not fields since these are
not visible at the interface level. When seen from an-
other class or interface with a larger alphabet, the F
invariant must hold on the alphabet of F. The local
history h of a class/interface is the time sequence of
communications events seen by this object. We define
the events below:
a method call made by this object, denoted this
o.m(e)
a method call received by this object, denoted o
this.m(e) (for m in the class)
a method return made by this object, denoted o
this.m(e;e) (for m in the class)
a method return received by this object, denoted
this o.m(e;e), as well as
a creation event made by this object, denoted
this o. new C(e)
where o represents the other part in the communica-
tion. In the last event, F is the declared interface of
the variable receiving the new object, i.e., the inter-
face of v in the statement v := new C(e). In practice,
specifications using histories will often be concerned
about method completions, i.e., and events,
since these capture input/output relations. For a given
method call, the event precedes the event, which
is formalized by a wellformedness predicate.
Sequence Notation: A sequence is either empty or
of the form q;x where q is a sequence and x an ele-
ment. The notation q/s denotes the projection of q re-
stricted to elements in the set s, q q
0
denotes that q is
a prefix (head subsequence) of q
0
, x before x
0
in q
denotes that x appears before any occurrence of x
0
in
q, i.e., length(q
0
/x) length(q
0
/x
0
) for any prefix q
0
of q. For a global history H, there must be a meaning-
ful ordering of the events:
(o o
0
.m(e)) before (o o
0
.m(e)) in H
(o o
0
.m(e)) before (o o
0
.m(e;e)) in H
(o o
0
.m(e;e)) before (o o
0
.m(e;e)) in H
(o
0
o. new C(e)) before (o o
00
.m(e
0
)) in H
(o
0
o. new C(e)) before (o
00
o.m(e
0
)) in H
expressing that messages are sent before they are re-
ceived, that method invocation must precede method
return, and that a creation event of o must precede
other o events. The conjunction of these properties
(universally quantified) expresses the wellformedness
predicate, used in the compositional rule for global
reasoning. The rule for object composition essentially
says that the global invariant is the conjunction of the
wellformedness predicate and all object interface in-
variants, each referring to its own alphabet. Since
the alphabets of the objects are by definition disjoint,
the wellformedness predicate is needed to connect the
different object invariants.
For a local history h, we let h/F denote the pro-
jection to events visible through F, i.e., events of the
form this o. new C(e), this o.m(e), and this
o.m(e;e), as well as events of the form o this.m(e)
and o this.m(e;e) for m offered by F. The same no-
tation applies to classes C, thus this.m and this.m
events are restricted to methods defined or inherited in
the class. An invariant I(h) of an interface F is under-
stood as I(h/F) in a subinterface or class. We there-
fore define I
F
(h) as I(h/F), and similarly for classes.
2.2 An Example with Reasoning
Figure 2 shows a minimalistic example defining a
class BANK and a subclass BANKPLUS and related in-
terfaces. Interface Bank states that the balance (as re-
turned by bal) is the sum of amounts deposited (by
add) or withdrawn (by sub) from the bank account,
ignoring unsuccessful add and sub calls. In addition
it states that add calls always succeed. Interface Per-
fectBank extends Bank by stating that also sub calls
MODELSWARD 2019 - 7th International Conference on Model-Driven Engineering and Software Development
180
succeed, while interface BankPlus extends Bank by
stating that balance is always non-negative.
The code illustrates suspension, non-blocking and
blocking calls, static and late bound calls. Interface
and type names are capitalized while class names are
written in upper case letters. The specification of
interface Bank illustrates history-based specification.
In the inductive definition of sum, others is used to
match other cases, and underscore (_) is used to match
any expression. The keyword inv identifies invari-
ants and where identifies auxiliary function defini-
tions. In assertions, inv refers to the current in-
variant, while C : inv refers to the invariant of class
C. The auxiliary function sum calculates the balance
from the local history. Note that only method-return
events are used in the specification.
The subclass BANKPLUS inherits the pre/post spec-
ifications of bal and add from BANK, but not the ones
for upd and sub, which are redefined and therefore not
inherited. In fact the subclass violates the pre/post
specifications for upd and sub in BANK. BANKPLUS
does not support the BANK interface PerfectBank.
Therefore the implements clause is redefined and
not inherited. In this example, the subclass does not
obey the requirements imposed by behavioral sub-
typing, nor by lazy behavioral subtyping. The re-
definition of upd in BANKPLUS does not satisfy the
BANK postcondition of upd, and therefore the verifica-
tion of the redefined upd will not succeed when using
the framework of lazy behavioral subtyping (since the
BANK postcondition of upd is needed for the local upd
calls in the verification of BANK and therefore pushed
to subclasses). In our framework, the BANK postcon-
dition of upd is not imposed on the subclass, and
therefore the example can be verified without prob-
lems.
Figure 3 shows a subclass of BANKPLUS that could
be meaningful in a distributed setting. A transaction
is delayed as long as the balance is insufficient. This
is done by means of an await statement, which sus-
pends the sub activation, but does not block the ob-
ject. Note that sub is inherited but not its specifi-
cation. Class BANK2 implements the additional in-
terface PerfectBank, and it inherits from BANKPLUS
the invariant and all pre/post specifications, except the
ones for upd and sub, which are violated. Again rea-
soning with behavioral subtyping or lazy behavioral
subtyping breaks down, because the reasoning about
the (late bound) calls to upd in BANK depends on the
postcondition of upd, and therefore it is imposed on
all subclasses in the case of lazy behavioral subtyp-
ing. Our framework allows flexible reuse of code and
specifications, without verification problems, avoid-
ing harmful superclass requirements.
interface Bank {
Bool sub(Nat x)
Bool add(Nat x) [true, return= true]
Int bal() [true, return= sum(h)]
where
sum(empty) = 0,
sum(h;(_this.add(x;true))) = sum(h)+x,
sum(h;(_this.sub(x;true))) = sum(h)x,
sum(h;others) = sum(h) }
interface PerfectBank extends Bank {
Bool sub(Nat x) [true, return= true] }
interface BankPlus extends Bank {
inv sum(h)>=0 }
class BANK implements PerfectBank {
Int bal:=0;
Bool upd(Int x) {bal:=bal+x;
return true} [true, return= true]
[inv, bal=sum(h)+x and return=true]
Bool add(Nat x){return this.upd(x)}
[true, return= true]
Bool sub(Nat x){return this.upd(x)}
[true, return= true]
Int bal(){return bal} [true, return=bal]
inv bal=sum(h) }
class BANKPLUS implements BankPlus
inherits BANK{
Bool upd(Int x) {Bool ok:=(bal+x>=0);
if ok then ok:=BANK:upd(x) fi;
return ok} [inv, bal>=0 and bal=
sum(h)+if return then x else 0]
[b’=bal, return=(b’+x>= 0)]
Bool sub(Nat x) [b’=bal, return=(b’>= x)]
inv BANK:inv and bal >=0 }
class CLIENT{Seq[String] paid;
Bank acc:= new BANK;
Bool salary(Nat x){return acc.add(x)}
Bool bill(String kid, Nat x, Bank y){
Bool ok:=false;
if kid 6∈ paid then await ok:=acc.sub(x);
if ok then y.add(x); paid:=(paid;kid) fi
fi; return ok}
inv paid=allpaid(h)
where
allpaid(empty) = empty,
allpaid(h;_this.bill(k,x,y;true))=(allpaid(h);k),
allpaid(h; others)=allpaid(h) }
Figure 2: A simple bank example including a possible
client class, where paid corresponds to successful bill pay-
ments. The call BANK:upd uses static binding.
A Flexible Framework for Program Evolution and Verification
181
class BANK2 implements PerfectBank,
BankPlus inherits BANKPLUS {
Bool upd(Int x)
{await bal+x>=0; bal:=bal+x; return true}
[inv, bal=sum(h)+x and bal>=0 and return]
Bool sub(Nat x) [true, return= true] }
Figure 3: A subclass of class BANKPLUS.
Consider next that class BANKPLUS is changed for in-
stance by redefining sub by
Bool sub(Nat x) { return BANK:sub(x+1)}
A fee of 1 unit is incorporated in the withdrawal.
In this case, class BANKPLUS can still be reverified,
but the subclass BANK2 is indirectly affected by this
change, and it is no longer a PerfectBank (because of
the fee). Thus to avoid this inconsistency, class BANK2
should be modified (say by removing PerfectBank as
an interface).
If a subclass of BANK redefines add and sub with-
out using upd, that subclass may remove method upd.
And a subclass of BANK implementing an interface
with add, but not sub, and with the same class in-
variant as class BANKPLUS, may remove method sub
in order to make invariant reasoning simpler.
2.3 Dynamic and Static Binding
Dynamic (late) binding implies that a method call to
m may behave differently depending on the class of
the object executing the method (called the actual
class), even for calls that bind to the same defini-
tion, say the one found in a class C. For C1 and
C2 subclasses of C, it may be that there is a local
call this.n(x) in method m of C, and if n is redefined
in both C1 and C2, an m call will bind n differently
depending on the actual class. For instance in the
Bank example, a call to sub binds to BANK :sub, but
the this.upd call in the body of BANK :sub binds to
BANKPLUS : upd or BANK : upd depending on the
class of the executing object, BANKPLUS or BANK,
respectively.
We let the function bind(C, m) return C if C has a
definition of m, otherwise the closest superclass of C
with a definition of m. When C is known, bind(C,m)
can be calculated, even with an open-ended class hier-
archy. For a static call D:m(. . .), the binding is known
statically (given by bind(D, m)), and we assume that
such a call is replaced by bind(D,m) : m(...) during
type checking. This makes the binding explicit. This
means that bind(C,D:m) equals bind(D,m) when C is
a subclass of D. For a late bound local call this.m(...)
appearing in a class C, the binding of m can be calcu-
lated statically for a given actual class of this.
In the approach of verification based on behavioral
interface subtyping, we reconsider the call for each
possible actual class of this. Thus for each subclass
of C (defined so far), we reconsider the verification of
any inherited or reused methods. For each subclass
C
0
, the binding can then be done at verification time,
binding this.m to bind(C
0
,m) and C :m to bind(C,m).
A complication in reasoning about local calls is
that a release point (programmed by an await state-
ment) should maintain the invariant of the actual class
(say D) as opposed to the enclosing class (C). Thus
reasoning about a release point occurring in a method
C : m must consider the invariant of the actual class,
which may be a subclass (D) of C. We therefore index
the derivation symbol (`) with the class of the execut-
ing object (as in `
D
), which is possible with behav-
ioral interface subtyping since all relevant proof obli-
gations are reconsidered for each subclass. In reason-
ing with behavioral subtyping, this is not needed since
reasoning about method m of C is made (once) for all
actual D. The latter approach makes reasoning sim-
ple (when it succeeds), whereas in our system, based
on behavioral interface subtyping, we may differen-
tiate the different versions of an inherited method in
the different subclasses. This gives more fine-grained
control in the reasoning, which is valuable in the set-
ting of code reuse and program evolution. A pre/post
specification of m in C will be based on the invariant
of C, which may be different from that of D. There-
fore a pre/post specification of m in C can not in gen-
eral be guaranteed in a subclass D if C : m has local
calls or release points.
For example, consider two late bound m calls with
C1 and C2 as the actual classes. These can be referred
to by C1 : m and C2 : m, respectively. We have that
bind(C1, m) = bind(C2,m) when the closest defini-
tion of m is in a common superclass of C1 and C2. A
call to m with C as the actual class may cause a local
call this.n(y) (directly or indirectly). In the verifica-
tion, this call will then be re-analyzed with C as the
actual class, using the binding bind(C,n), and a static
call D : n(y) will be re-analyzed with C as the actual
class using the binding bind(D,m). The analysis of
these call is the same when bind(C,m) = bind(D,m)
and the method body has no local calls to methods
redefined below D and no release points.
In order to do Hoare-style reasoning, we use a sys-
tem for deriving theorems of the form
`
C
[P] s [Q]
where C is the actual class, and the Hoare triple
[P] s [Q] states that if the statement(list) s is executed
in a state satisfying the precondition P the final state
MODELSWARD 2019 - 7th International Conference on Model-Driven Engineering and Software Development
182
will satisfy the postcondition Q provided the execu-
tion of s terminates. Figure 4 presents sample proof
rules needed for the example. Note that the axiom
schema for assignment is as for sequential programs
without aliasing. (If we had allowed remote field ac-
cess, this would no longer hold.) The notation Q
v
e
de-
notes (capture-free) textual substitution replacing all
free occurrences of the variable v by the expression e.
Similarly, Q
v,v
0
e,e
0
denotes simultaneous replacement (v
by e and v
0
by e
0
). Rules for sequential composition
and if-statements are as usual. Rules for while-loops
and recursive calls are also standard, but are omitted
here for brevity.
For a class C we use `
C
to prove the pre/post
verification conditions for objects of that class. For
code in class C this corresponds to normal class-
based reasoning. For code inherited by C, reason-
ing about release points and local or self calls de-
pends on C, which reflects the actual class of this ob-
ject. According to rule self call, the call v := this.m(x)
is equivalent to the local call v := C : m(x) where
C is the class of this object. Rule local call states
that reasoning about v := B : m(e) reduces to reason-
ing about body
B:m
, adding effects on the history, and
where body
B:m
denotes the body of the definition of
method m in class B. The body of a method definition
m(x){s; return e} is given by
h := (h;caller this.m(x));
s;return := e;
h := (h;caller this.m(x;return))
incorporating the effects on the local history reflecting
method call reception and method return.
Since each class is analyzed separately, we ob-
tain a modular and incremental verification system
suitable for an open-ended class hierarchy, not un-
like (Din and Owe, 2014). In the analysis of a class
C we may need to consider superclasses of C, but
not subclasses. We may reuse superclass verification
results as follows: For code inherited from a super-
class B, we may derive `
C
[P] s [Q] from `
B
[P] s [Q]
when s has no release points and no local calls lead-
ing to calls of methods redefined below B. Other-
wise `
C
[P] s [Q] can be established by a new anal-
ysis of s and of any locally called methods in s.
In particular `
C
[P] v := B :m(x) [Q] follows from `
B
[P] v := B :m(x) [Q] when B : m has no release points
or local calls. In contrast to behavioral subtyping and
lazy behavioral subtyping, no requirements are im-
posed on subclasses.
3 PROOF OBLIGATIONS
For a given program we must ensure that each class
C satisfies the stated requirements of C, i.e., that the
implements clauses are satisfied (syntactically and
semantically), that the class invariants are maintained
by each method (except private ones), and that the
stated pre/post specifications are satisfied by the cor-
responding methods of the class. In this proof, in-
herited methods must be considered, while superclass
implementation claims, superclass invariants, and su-
perclass pre/post specifications, are not considered
(unless inherited).
Each class is verified in this sense (taking inher-
ited superclass code into consideration). Together
with correct typing of object variables, this ensures
that each object variable will satisfy its declared in-
terfaces, and each object of a (run-time) class C will
satisfy the interfaces of C. This ensures that the com-
positional rule for reasoning about concurrent object
systems is sound. Furthermore, each late bound local
call made at run-time with C as the run-time class of
the caller/callee will satisfy the pre/post specification
given in C. since class C is (statically) verified. This
is reflected in the composition rule which considers
all verified callee classes.
We formalize the proof obligations expressing the
correctness of a program, class, interface claim, class
invariant, and method specification. We consider the
following proof obligations, where each obligation
identifies the relevant class C:
Program and Class Correctness
A program P is correct, denoted ` P ok , if each class
in the program is correct.
A class C is correct, denoted ` C ok , iff
` C sat F for each interface F specified in
the implements clause of C,
` C inv I for each stated (or explicitly in-
herited) invariant I of C,
`
C
m(x) sat [P,Q] for each method m(x) de-
fined (or inherited) in C, and each specifica-
tion [P,Q] stated (or inherited) in C for m,
where `
C
m(x) sat [P,Q] is verified by proving
`
C
[P] body
bind(C,m):m
[Q]
(as above), and ` C inv I is verified by proving
`
C
m(x) sat [I, I]
for each public method m(x) (possibly inherited) in
C and that the invariant holds initially, i.e., I holds
when h is replaced by the empty history and fields
by initial values; and finally, ` C sat F is verified by
A Flexible Framework for Program Evolution and Verification
183
assign `
C
[Q
v
e
] v := e [Q]
history `
C
[h
0
= h] s [h
0
h]
await guard `
C
[I
C
L] await b [b I
C
L]
new `
C
[v
0
.fresh(v
0
,h) Q
v,h
v
0
,h;(thisv
0
. new C(e))
] v := new C(e) [Q]
simple call `
C
[Q
h
h;(thiso.m(e))
] o.m(e)[Q]
blocking call `
C
[v
0
.o 6= this Q
v,h
v
0
,h;(thiso.m(e));(thiso.m(e;v
0
))
] v := o.m(e) [Q]
non-blocking call
`
C
[P] await true [v
0
.Q
v,h
v
0
,h;(thiso.m(e;v
0
))
]
`
C
[P
h
h;(thiso.m(e))
] await v := o.m(e) [Q]
self call
`
C
[P] v := bind(C, m) : m(e) [Q]
`
C
[P] v := this.m(e) [Q]
implicit self call
`
C
[P] v := bind(C, m) : m(e) [Q]
`
C
[o = this P] v := o.m(e) [Q]
local call
`
C
[P] body
B:m
[Q
h
h;(thisthis.m(e;v))
]
`
C
[P
x,caller,h
e,this,h;(thisthis.m(e))
L] v := B : m(e)[Q
x,caller,return
e,this,v
L]
sequence
`
C
[P] s [Q] `
C
[Q] s
0
[R]
`
C
[P] s;s
0
[R]
if-then-else
`
C
[P b] s [Q] `
C
[P ¬b] s
0
[Q]
`
C
[P] if b then s else s
0
fi [Q]
Figure 4: Hoare style rules and axioms. Primed variables represent fresh logical variables, fresh(v
0
,h) expresses that v
0
does not occur in h, and L denotes a local assertion, i.e., without occurrences of fields. In rules self call and static local call,
we assume that v does not occur in e. In rule local call we assume that x is the formal parameter list (which is read only).
Note that bind(C,m) is calculated at verification time.
proving that the conjunction of the invariants I
i
(h) of
C implies the invariant of F, I
F
(considering methods
visible through F, as explained in Section 2.1):
i
I
i
(h) I
F
(h/F)
Type checking ensures that all methods of F are of-
fered in C, with a signature better or equal to that of F
(i.e., contravariant parameter types and covariant re-
turn types). And type checking ensures that removed
methods are not directly or indirectly called by meth-
ods (possibly inherited) in C, and that private methods
of C are not directly or indirectly called with await .
For a subclass C
0
of C, `
C
m(x) sat [P,Q] need
not imply `
C
0
m(x) sat [P,Q] even if m is not rede-
fined, since the binding of local calls appearing in
the body of m in C may bind differently in the con-
text of C
0
(i.e., bind(C, n) versus bind(C
0
,n), respec-
tively). In general `
C
m(x) sat [P,Q] depends on re-
definition of m or locally called methods and possi-
ble C invariants in case of suspension (by await
statements). A redefinition in C
0
of a locally called
method may violate the supertype specification of that
method. A suspension point performed on a C
0
object
can only guarantee that the C
0
invariant is maintained,
which could be weaker than the C invariant. We there-
fore track these dependencies, and we may conclude
that `
C
m(x) sat [P,Q] implies `
C
0
m(x) sat [P,Q] if
`
C
m(x) sat [P,Q] does not depend on any redefined
code and that any invariant used in the verification is
respected by C
0
.
For a method m(x) inherited in C from a super-
class B, we may write
`
C
B:m(x) sat [P,Q]
rather than `
C
m(x) sat [P,Q] to visualize that
the method comes from the superclass B. Since
MODELSWARD 2019 - 7th International Conference on Model-Driven Engineering and Software Development
184
bind(C, B : m) equals bind(B, m), this is consis-
tent with the definition of `
C
m(x) sat [P,Q]. For
a method C : m without local calls or suspension
points, we have that `
C
0
C :m(x) sat [P,Q] reduces
to `
C
m(x) sat [P,Q]. This gives a practical way of
reusing proofs from superclasses.
4 SOFTWARE CHANGES
We now consider changing a system, i.e., changing
existing classes and adding new classes and inter-
faces. For instance, one may introduce a new inter-
face to make two independent subsystems interact,
adding support of the new interface in one or more
existing classes. An existing class D may be changed
by replacing methods, changing inheritance clauses,
implementation clauses, and/or specifications. This
can be understood by replacing the whole class defi-
nition by another definition. The updated class D may
in general have a number of subclasses (at the time
when D is updated) and these are implicitly modified
if they inherit/reuse code from D. Thus, we need to
reverify the redefined D, but in addition we need to
consider the affected subclasses of D.
We use the following syntax for defining class up-
dates: update Cl, where the meta-symbol Cl de-
fines the syntax for class definitions, as in Figure 1.
Thus, a class update
update class D [([T cp]
+
)]
?
[ implements F
+
]
?
[ inherits C(e)]
?
[ removing m
+
]
?
{[T w [:= r]
?
]
s
?
M
S
I
}
modifies an existing class D by adding class parame-
ters cp
+
(if present), changing the interface support
to F
+
(if present), adding superclasses [C(e)]
+
(if
present), removing methods m
+
(if present), adding
fields w
+
(if present), adding initialization code s
(if present), adding/redefining method definitions M
(if present), changing method specifications S
(if
present), and changing the invariant to I
(if present).
For any optional item omitted, there is no change from
the original class. This is somewhat similar to the se-
mantics of inheritance, except that the modifications
are made on an existing class rather than a new sub-
class.
We consider correctness of the updated code, and
avoid complications such as run-time upgrades where
new and old versions of the updated code are part of
the running system. As before we assume type cor-
rectness. Consider that the redefined D (let us refer
to it as
ˆ
D) implements some interfaces, which may or
may not be the same as for D. If
ˆ
D includes all in-
terfaces of D, all type correct calls to D objects will
be type correct and supported by
ˆ
D objects as well;
and if the interface specifications are the same, global
reasoning from interface specifications of D objects is
not violated by replacing D objects by
ˆ
D objects.
Consider next the case that a class is modified by
removing the support for an interface. In this case the
statement v := new D, becomes illegal when class D
is modified so that it no longer supports the interface
type of variable v. We may then change the statement
to v:= new B where B supports the interface. In gen-
eral we may need a sequence of changes in order to
obtain a desired resulting program, including changes
to C and other classes using D. (Subclasses that in-
herit the interface clause of D may then explicitly add
support for the interface, when desirable.)
The verification obligations caused by the redefi-
nition consist of verifying `
ˆ
D ok and reverification
of the subclasses of D since they may be affected by
the change. We first mark the obligation ` D ok ,
as well as all sub-obligations, as pending. And for
each subclass D
0
we mark the obligation ` D
0
ok , as
well as all sub-obligations, as pending. Verification of
`
ˆ
D ok is then done as defined above for the class re-
sulting from the update, and the subclasses of D must
be reverified. If an obligation depends on a pend-
ing sub-obligation, one should consider the latter first.
Since subclasses may depend on classes defined ear-
lier (as substantiated by Theorem 1 below), we recon-
sider the subclasses in the order defined. For a sub-
class D
0
, the obligation ` D
0
ok should be marked as
pending if the proof made use of a result from D, say
`
D
m(v) sat [P,Q]. For each such D result, it suffices
to prove `
ˆ
D
m(v) sat [P,Q]. If all sub-obligations of
` D
0
ok can be reverified in this manner, the obliga-
tion ` D
0
ok is marked correct.
The state of a proof obligation indicates whether
it has been proved or not. We consider the states: cor-
rect, incorrect, pending. These express respectively
that the obligation is verified, that the (old) proof is no
longer valid, that verification remains to be done. As
explained above, if a pending obligation can be veri-
fied or be reduced to a correct obligation, its state can
be reset to correct. If a pending obligation cannot be
verified, its state can be set to incorrect. In some cases
it may be possible to reverify the obligation using ad-
ditional specifications of inherited or called methods,
but in general this may require human insight. Other-
wise further modifications are needed.
An advantage of our approach is that violations
caused by superclass modifications can be handled,
called modification independence:
A Flexible Framework for Program Evolution and Verification
185
Theorem 1 (Modification Independence).
Assume that a class C is affected by a superclass mod-
ification such that some inherited superclass specifi-
cations are violated in C. Then C can be modified
such that there is no violation.
Proof. Let [P,Q] be a violated m-specification. If this
specification is inherited, we simply change C by not
inheriting it and then the specification is no longer re-
quired in C. And if [P,Q] is stated in C, we remove
the specification. In case [I,I] is then removed for an
invariant I, we also remove the invariant from C, and
remove any interface of C depending on the invariant.
We may repeat this process until all violations are re-
moved.
This means that any undesired requirements due to
modifications in a superclass can be removed. Af-
ter removal one may add desired requirements and
then verify these requirements (modifying the class if
needed). In this way one may reverify that the stated
interfaces are satisfied. This gives full flexibility of
properties while doing modifications, at the cost of
reconsidering subclasses in case the modifications re-
quire changes in subclasses.
Our approach may result in some verified classes
and some classes that are not yet verified. In this
imperfect setting we may still reason about the over-
all system by using the following formulation of the
global system invariant I(H) over the global history
H:
I(H) , wf(H)
^
o. new C(_)H
^
FC
I
F
(H/o)
this
o
where C is restricted to range over classes that are
tagged correct, i.e., those satisfying ` C ok . The
last conjunction ranges over all interfaces F imple-
mented by C. Here wf (H) denotes the welldefined-
ness predicate, expressing the before ordering be-
tween events, given in Section 2.1.
This global invariant captures the partial knowl-
edge of the global history H given by the interface
invariants of the objects appearing in the system (pos-
sibly dynamically generated) considering only objects
of correct classes. This global reasoning rule essen-
tially turns off the interface invariants for the non-
correct classes.
Limitations: We assume type correctness. Thus
program changes must result in type correct pro-
grams. Removal of declarations of fields, methods,
parameters, and variables is only allowed when not in
use. Secondly we do not consider changes in an in-
terface I. This can be simulated by adding the new
version of I as a separate interface, making changes
wherever I (or a subinterface) is used, and then re-
moving the original I when no longer referred to.
Examples of Software Changes on BANK
Assume now that class BANK is changed so that upd
calls check, which returns true.
update class BANK implements PerfectBank{
Bool check(Int x){return true} [true, return]
Bool upd(Int x){Bool ok:=check(x);
if ok then bal:=bal+x fi; return ok}
[inv, bal=sum(h)+x and return=true] }
All other aspects of class BANK are kept unchanged,
including all BANK specifications. Thus inv refers
to the original invariant of BANK. Since check returns
true, the verification of upd can be reused, and the
other verification conditions of BANK are as before
and need not be reverified. And the verification of
the added local method check is trivial. Furthermore,
the subclasses are not affected by this change. Thus
the verification conditions caused by the class update
are straightforward.
However, if class BANKPLUS is changed by re-
defining check(x) as in
update class BANKPLUS
{ Bool check(Int x){return bal+x>0} }
the local late bound call to upd(x) in the inherited
method sub results in the value of bal x > 0 to be
returned from sub. Again verification conditions are
straightforward. In contrast this could not be verified
in the frameworks of (Dovland et al., 2015; Dovland
et al., 2012).
Adding a side effect in check such as if x <
0 then bal := bal 1 fi would destroy the BANK
invariant, but not the BANKPLUS invariant. Then the
former should be removed.
Consider next the following update of class BANK
with a redefinition of sub:
update class BANK{ Bool sub(Nat x)
{ bal:=balx; return true} }
The new version of BANK inherits the old interface
(PerfectBank), the methods add, bal, and upd, the old
invariant, the old specification of sub (i.e., postcon-
dition retur n = true). The proof obligations amount
to first verifying that the redefined sub maintains the
invariant and satisfies the postcondition. This is triv-
ial. Secondly it must be verified that each subclass
is still ok . As subclass BANKPLUS now may allow
a negative balance, the BANKPLUS invariant bal 0
cannot be verified (because it is incorrect). We may
MODELSWARD 2019 - 7th International Conference on Model-Driven Engineering and Software Development
186
still do (limited) global invariant reasoning about a
system containing BANKPLUS objects.
To solve this inconsistency in BANKPLUS, we may
update this class by removing the support of interface
BankPlus and removing the last conjunct of the invari-
ant and the spec. of sub, and then reverify. Alterna-
tively, we may change BANKPLUS by redefining sub
so that the old specifications can be reverified.
Finally, the redefinition of sub in Section 2.2 can
be handled by removing interface PerfectBank in class
BANK2 and checking/adjusting any usage of new
BANK2 (as PerfectBank) in other classes.
5 RELATED WORK
In formal methods, the notion of refinement is used to
reflect software development. A refinement is in gen-
eral leading from a design with certain properties to a
design which preserves these properties while adding
more detail. In this way refinement is semantics-
preserving (Ward and Bennett, 1995). Certain re-
finement logics support the addition of error values,
thereby semantics is preserved as long as no errors
appear. Banach et al. have argued for the need of
refinement-like steps that go beyond the limitation
of semantics-preserving development (Banach et al.,
2007). But their approach does not support analysis
of program properties. Hu and Smith (Fu and Smith,
2008; Fu and Smith, 2011) consider verification of
evolving Z specifications. However, they do not look
at changes to classes that may affect global system
properties.
In the setting of object-oriented programs with in-
heritance, behavioral subtyping is the most common
reasoning approach, restricting subclasses to obey the
super-class specifications (Liskov and Wing, 1994).
This means that subclasses must preserve behavior.
Lazy behavioral subtyping (Dovland et al., 2010;
Dovland et al., 2011) relaxes this condition; only be-
havior that is needed to verify local calls in a super-
class must be respected by a subclass redefining the
method. This gives added flexibility, allowing a larger
class of changes without breaking the requirements.
Interface abstraction allows reasoning about re-
mote calls to rely on the declared interface of the
callee. This means that changes in a (super)class im-
plementation may be done as long as the stated inter-
face support is respected, and as long as subclass rea-
soning is not affected. A calculus allowing changes
to methods, (super)classes and interfaces is presented
in (Dovland et al., 2012), based on lazy behavioral
subtyping. Program properties, represented by Hoare
triples, are classified in two categories for each class
C, representing the verified ones and the unresolved
(unverified) ones, U(C). The set of verified properties
of a given class C and method m is denoted G(C,m).
When the set of unverified program properties is veri-
fied (i.e., U(C) is empty) the class is found to be cor-
rect in the sense that all pre/post method specifica-
tions are satisfied by the corresponding implementa-
tion in a class as well as those in interfaces supported
by the class. Changes in code or specifications may
affect both categories. However, a program require-
ment added to U(C) may be impossible to verify (in
case the Hoare triple is not satisfied), and it will then
remain in U(C), and there is no guarantee that this
problem is detected.
The approach in (Dovland et al., 2015) addresses
transformation of classes and allow classes in the mid-
dle of a class hierarchy to be changed. Modifications
are archived by means of update operations modify
and simplify. The modify operations extend class def-
initions, allowing code such as new fields, method
definitions, guarantees, and interfaces to be added to
classes, and existing methods to be redefined. The
simplify operation allows redundant methods to be re-
moved from class definitions. The approach does not
classify classes using G and U such as in (Dovland
et al., 2012), rather, for each update applied to a class,
all verification work is done to methods affected by
the update. But, any superclass requirements needed
to handle local calls are imposed on subclasses, as
in (Dovland et al., 2012).
A number of works on asynchronously commu-
nicating concurrent objects consider certain forms of
software and/or specification changes: The concept
of dynamic software updates allows changes to (su-
per)classes during run-time (Johnsen et al., 2005). A
challenge with run-time upgrades of distributed sys-
tems is the need to allow updates in a distributed man-
ner, and thereby allowing coexistence of different ver-
sions of the software (Ajmani et al., 2006; Johnsen
et al., 2005; Seifzadeh et al., 2013). In contrast to
these works, we are focusing on the reasoning as-
pects. Bannwart and Müller (Bannwart and Müller,
2006) consider program changes through refactoring,
and show how to preserve external behavior for a class
of non-trivial refactoring. However, they do not in-
clude changes that violate behaviors.
Another line of work considers proof reuse, in-
cluding partial reuse of proofs of earlier verified prop-
erties. This may require some storage of proof out-
lines or non-trivial verification steps. This means that
when a module is corrected, one may try to rerun pre-
vious proofs to alleviate the verification burden (Reif
and Stenzel, 1993). The notion of abstract method
calls allows reuse of abstract proof outlines, for a
A Flexible Framework for Program Evolution and Verification
187
fixed method body, while their instances may need
further work when other methods or requirements are
changed (Bubel et al., 2016; Hähnle et al., 2013). A
related approach is the use of symbolic predicates to
express requirements to general properties for a given
program without knowing what the concrete proper-
ties are (Din et al., 2018). These approaches sim-
plify the verification task of evolving programs. The
amount of proof reuse can be balanced against the
amount of automation. Efficiently automated proofs
need not be reused while interactive proofs could ben-
efit from reuse, if possible. Our approach is oriented
towards a language with a high degree of automation
of verification conditions, and proof reuse is there-
fore not our focus. A recent work by Ulewicz et
al. (Ulewicz et al., 2016) supports a tight integration
of verification of unchanged behavior (regression ver-
ification) with that of changed behavior (delta verifi-
cation); but unrestricted changes are not supported.
The considered concurrency model is used by a
number of languages supporting active objects, in-
cluding ABS and Encore. The core language used
here is avoiding the use of futures, in order to sim-
plify the basic reasoning rules for method calls, as
discussed in a recent paper (Karami et al., 2019).
6 CONCLUSION
We have shown a flexible framework for reasoning
about program reuse and evolution, considering an
imperative setting of distributed object-oriented com-
ponents. Flexibility with respect to reuse and inher-
itance, beyond the limitations of behavioral subtyp-
ing, requires that all objects are seen through an inter-
face (interface abstraction). Our approach builds on
the principle of behavioral interface subtyping, where
each class must support its declared interfaces, but
need not support interfaces of superclasses. This al-
lows the class hierarchy to be used for code reuse
while the interface hierarchy is used for behavioral
reuse. In contrast to lazy behavioral subtyping, no su-
perclass requirements are imposed on subclasses by
the framework. This gives a more flexible frame-
work for software modifications than that of (Dov-
land et al., 2015; Dovland et al., 2012) since meth-
ods can be redefined without restrictions caused by
superclasses. This means that we may deal with soft-
ware changes that cannot be verified with approaches
building on lazy behavioral subtyping. The example
demonstrated this. We are avoiding inconsistencies
that are inherent in frameworks building on behav-
ioral subtyping/lazy behavioral subtyping.
In our framework, modifications to a class C lead
to reverification of that class, and subclasses must be
reconsidered when they inherit modified parts of C,
but superclasses need not be reverified. During rever-
ification, proofs can be reused as much as possible,
and further changes to the class and/or subclasses may
be done as needed.
Our framework considers the setting of active,
concurrent objects, for which Java code can be gen-
erated. We have demonstrated that Hoare-style rea-
soning is quite simple for this setting, in the sense that
reasoning is like sequential reasoning, with sequential
effects on the history added.
Our language supports late bound method calls.
However, for local method calls the language supports
static local calls as well as late bound local calls. The
notion of static local calls is needed in the framework
to reduce verification conditions about late bound lo-
cal calls to verification conditions about static local
calls. Our framework gives fine-grained control of
reused code, where the handling of local calls, both
late bound and static ones, as well as suspension, is
essential. Static local calls are also useful in program-
ming, avoiding the fragile base class problem since
the binding is fixed for such calls.
We build on results from (Owe, 2016) concerning
class inheritance. In contrast to that work, we con-
sider here program changes and evolution, supporting
modification independence (Theorem 1), and give a
reasoning rule for partially reverified systems. In ad-
dition we provide here more fine-grained control of
reused code, and a simplified treatment of (static and
late bound) local calls.
Our framework can be generalized to multiple
inheritance, which is supported by behavioral inter-
face subtyping. And the framework may be extended
to reason about dynamic (run-time) class upgrades,
assuming existing objects are upgraded in invariant
states, as in Creol (Johnsen et al., 2005), where new
calls run renewed code and suspended old calls run
old code. The new invariant must imply the old invari-
ant, and it must be verified that old methods maintain
the new invariant. This ensures that the interleaving
of new code and remaining old code is not harmful.
The requirements to an upgraded class are strength-
ened by these requirements, whereas the requirements
to subclasses are as described above.
REFERENCES
Ajmani, S., Liskov, B., and Shrira, L. (2006). Modular soft-
ware upgrades for distributed systems. In Thomas,
D., editor, ECOOP 2006 – Object-Oriented Program-
ming, pages 452–476. Springer.
MODELSWARD 2019 - 7th International Conference on Model-Driven Engineering and Software Development
188
Banach, R., Poppleton, M., Jeske, C., and Stepney, S.
(2007). Engineering and Theoretical Underpinnings
of Retrenchment. Science of Computer Programming,
67(2-3):301 – 329.
Bannwart, F. and Müller, P. (2006). Changing programs
correctly: Refactoring with specifications. In Misra,
J., Nipkow, T., and Sekerinski, E., editors, FM 2006:
Formal Methods, 14th International Symposium on
Formal Methods, Hamilton, Canada, August 21-27,
2006, Proceedings, volume 4085 of Lecture Notes in
Computer Science, pages 492–507. Springer.
Bubel, R., Damiani, F., Hähnle, R., Johnsen, E. B., Owe,
O., Schaefer, I., and Yu, I. C. (2016). Proof reposi-
tories for compositional verification of evolving soft-
ware systems - managing change when proving soft-
ware correct. Transactions on Foundations for Mas-
tering Change, 1:130–156.
Clavel, M., Durán, F., Eker, S., Lincoln, P., Martí-oliet, N.,
Meseguer, J., and Talcott, C. (2008). Maude manual
(version 2.4).
Din, C. C., Johnsen, E. B., Owe, O., and Yu, I. C. (2018). A
modular reasoning system using uninterpreted predi-
cates for code reuse. Journal of Logical and Algebraic
Methods in Programming, 95:82–102.
Din, C. C. and Owe, O. (2014). A sound and complete rea-
soning system for asynchronous communication with
shared futures. Journal of Logical and Algebraic
Methods in Programming, 83(5-6):360–383.
Dovland, J., Johnsen, E. B., Owe, O., and Steffen, M.
(2010). Lazy behavioral subtyping. Journal of Logic
and Algebraic Programming, 79(7):578–607.
Dovland, J., Johnsen, E. B., Owe, O., and Steffen, M.
(2011). Incremental Reasoning with Lazy Behavioral
Subtyping for Multiple Inheritance. Science of Com-
pututer Programming, 76(10):915–941.
Dovland, J., Johnsen, E. B., Owe, O., and Yu, I. C.
(2015). A Proof System for Adaptable Class Hier-
archies. Journal of Logical and Algebraic Methods in
Programming, 84(1):37 – 53.
Dovland, J., Johnsen, E. B., and Yu, I. C. (2012). Track-
ing behavioral constraints during object-oriented soft-
ware evolution. In Margaria, T. and Steffen, B., ed-
itors, Leveraging Applications of Formal Methods,
Verification and Validation. Technologies for Master-
ing Change, volume 7609 of Lecture Notes in Com-
puter Science, pages 253–268. Springer.
Fu, Z. and Smith, G. (2008). Towards more flexible devel-
opment of z specifications. In 2008 2nd IFIP/IEEE In-
ternational Symposium on Theoretical Aspects of Soft-
ware Engineering, pages 281–288.
Fu, Z. and Smith, G. (2011). Property transformation under
specification change. Frontiers of Computer Science
in China, 5(1):1–13.
Hähnle, R., Schaefer, I., and Bubel, R. (2013). Reuse
in Software Verification by Abstract Method Calls.
In Bonacina, M. P., editor, Automated Deduction -
CADE-24 - 24th International Conference on Auto-
mated Deduction, Lake Placid, NY, USA, June 9-14,
2013. Proceedings, volume 7898 of Lecture Notes in
Computer Science, pages 300–314. Springer.
Johnsen, E. B. and Owe, O. (2007). An asynchronous com-
munication model for distributed concurrent objects.
Software & Systems Modeling, 6(1):35–58.
Johnsen, E. B., Owe, O., and Simplot-Ryl, I. (2005).
A dynamic class construct for asynchronous concur-
rent objects. In Steffen, M. and Zavattaro, G., ed-
itors, Proc. 7th International Conference on Formal
Methods for Open Object-Based Distributed Systems
(FMOODS’05), volume 3535 of Lecture Notes in
Computer Science, pages 15–30. Springer.
Johnsen, E. B., Owe, O., and Yu, I. C. (2006). Creol: A
type-safe object-oriented model for distributed con-
current systems. Theoretical Computer Science,
365(1–2):23–66.
Karami, F., Owe, O., and Ramezanifarkhani, T. (2019).
An evaluation of interaction paradigms for active ob-
jects. Journal of Logical and Algebraic Methods in
Programming, 103:154 – 183.
Liskov, B. H. and Wing, J. M. (1994). A behavioral no-
tion of subtyping. ACMTransactions on Programming
Languages and Systems, 16(6):1811–1841.
Owe, O. (2015). Verifiable programming of object-oriented
and distributed systems. In Petre, L. and Sekerinski,
E., editors, From Action System to Distributed Sys-
tems: The Refinement Approach, pages 61–80. Tay-
lor&Francis.
Owe, O. (2016). Reasoning about inheritance and unre-
stricted reuse in object-oriented concurrent systems.
In Ábrahám, E. and Huisman, M., editors, Integrated
Formal Methods - 12th International Conference, IFM
2016, Reykjavik, Iceland, June 1-5, 2016, Proceed-
ings, volume 9681 of Lecture Notes in Computer Sci-
ence, pages 210–225. Springer.
Owe, O. and Ryl, I. (1999). On combining object orien-
tation, openness and reliability. In Proc. of the Nor-
wegian Informatics Conference (NIK’99), pages 187–
198. Tapir Academic Publisher.
Reif, W. and Stenzel, K. (1993). Reuse of proofs in soft-
ware verification. In Shyamasundar, R., editor, Foun-
dations of Software Technology and Theoretical Com-
puter Science, volume 761 of Lecture Notes in Com-
puter Science, pages 284–293. Springer-Verlag.
Seifzadeh, H., Abolhassani, H., and Moshkenani, M. S.
(2013). A survey of dynamic software updating. Jour-
nal of Software: Evolution and Process, 25(5):535–
568.
Ulewicz, S., Ulbrich, M., Weigl, A., Kirsten, M., Wiebe,
F., Beckert, B., and Vogel-Heuser, B. (2016). A
verification-supported evolution approach to assist
software application engineers in industrial factory
automation. In 2016 IEEE Internat. Symposium on
Assembly and Manufacturing (ISAM), pages 19–25.
Ward, M. P. and Bennett, K. H. (1995). Formal methods
to aid the evolution of software. International Journal
of Software Engineering and Knowledge Engineering,
05(01):25–47.
A Flexible Framework for Program Evolution and Verification
189