Technical Report MJ: An imperative core calculus for

Technical Report MJ: An imperative core calculus for
Technical Report
UCAM-CL-TR-563
ISSN 1476-2986
Number 563
Computer Laboratory
MJ: An imperative core calculus for
Java and Java with effects
G.M. Bierman, M.J. Parkinson, A.M. Pitts
April 2003
15 JJ Thomson Avenue
Cambridge CB3 0FD
United Kingdom
phone +44 1223 763500
http://www.cl.cam.ac.uk/
c 2003 G.M. Bierman, M.J. Parkinson, A.M. Pitts
Technical reports published by the University of Cambridge
Computer Laboratory are freely available via the Internet:
http://www.cl.cam.ac.uk/TechReports/
Series editor: Markus Kuhn
ISSN 1476-2986
MJ: An imperative core calculus for Java and Java with effects
G.M. Bierman M.J. Parkinson A.M. Pitts
University of Cambridge Computer Laboratory,
J.J. Thomson Avenue, Cambridge. CB3 0FD. UK.
{gmb,mjp41,amp12}@cl.cam.ac.uk
Abstract
In order to study rigorously object-oriented languages such as Java or C] , a common practice is to define lightweight fragments, or calculi, which are sufficiently small
to facilitate formal proofs of key properties. However many of the current proposals for
calculi lack important language features. In this paper we propose Middleweight Java,
MJ, as a contender for a minimal imperative core calculus for Java. Whilst compact,
MJ models features such as object identity, field assignment, constructor methods and
block structure. We define the syntax, type system and operational semantics of MJ,
and give a proof of type safety. In order to demonstrate the usefulness of MJ to reason
about operational features, we consider a recent proposal of Greenhouse and Boyland to
extend Java with an effects system. This effects system is intended to delimit the scope of
computational effects within a Java program. We define an extension of MJ with a similar
effects system and instrument the operational semantics. We then prove the correctness
of the effects system; a question left open by Greenhouse and Boyland. We also consider
the question of effect inference for our extended calculus, detail an algorithm for inferring
effects information and give a proof of correctness.
1
Introduction
In order to understand the design of programming languages, and to develop better verification
methods for programmers and compiler writers, a common practice is to develop a formal
model. This formal model, or calculus, often takes the form of a small, yet interesting fragment
of the programming language in question. Recently there has been a number of proposals for
a core calculus for the Java programming language. Most notable is Featherweight Java [12],
or FJ, which is a core calculus intended to facilitate the study of various aspects of the Java
type system, including a proposal for extending Java with generic classes.
In contrast to the main motivation for FJ, we are as much interested in various operational
properties of Java, as in its type system. To this extent, FJ is an oversimplification as it is
simply a functional fragment of Java; many of the difficulties with reasoning about Java code
arise from its various imperative features. Thus in this paper we propose Middleweight Java,
or MJ, as a contender for a minimal imperative core calculus for Java. MJ can be seen as
an extension of FJ big enough to include the essential imperative features of Java; yet small
enough that formal proofs are still feasible. In addition to FJ, we model object identity, field
assignment, null pointers, constructor methods and block structure.
MJ is intended to be a starting point for the study of various operational features of objectoriented programming in Java. To demonstrate this utility we consider extending the MJ type
3
system with effects. An effects system can be used to delimit the scope of computational effects
within a program. Effects systems originated in work by Gifford and Lucassen [8, 16], and
were pursued by Talpin and Jouvelot [19, 20], amongst others. Interestingly most of these
systems were defined for functional languages with simple forms of state. 1 Greenhouse and
Boyland [10] have recently suggested how an effects system could be incorporated within Java.
The key difficulty is the interaction between the effects system and the abstraction facilities
(mainly the notions of class and subclass) that makes Java, and object-oriented programming
in general, attractive.
Although Greenhouse and Boyland give a precise description of their effects system and
a number of examples, they do not give a proof of correctness. Having formally defined our
MJ effects system and instrumented operational semantics we are able to prove it correct. In
addition, Greenhouse and Boyland leave the question of effect inference to “further work”.
Again we formally define an algorithm to infer effect annotations and prove it correct. Thus
our work in this paper can be seen as both an extension and a formal verification of their
proposal: our theory underpins their computational intuitions.
This paper is organised as follows. In §2 we give the syntax, type system and operational semantics of MJ. In §2.5 we outline a proof of type soundness; the details are given in
Appendix 5. In §3 we define MJe, which is an extension of MJ with effects in the style of
Greenhouse and Boyland. In §3.3 we outline a proof of correctness for the effects system of
MJe; again the details are given in Appendix 5. In §3.4 we consider the problem of effects
inference, define an inference algorithm and prove it correct. We survey some related work
in §4, and give conclusions and indications of further work in §5.
2
MJ: An imperative core Java calculus
In this section we define Middleweight Java, MJ, our proposal for an imperative core calculus
for Java. It is important to note that MJ is an entirely valid subset of Java, in that all MJ
programs are literally executable Java programs. Clearly this is a attractive feature of the MJ
design, although we found in a number of places that it complicated matters. An alternative
would be to allow extra-language features; for example, Classic Java uses annotations and let
bindings which are not valid Java syntax in the operational semantics [7].
In the rest of this section we present the syntax, type system and single-step operational
semantics for MJ. We conclude the section with a proof of correctness of the type system.
2.1
Syntax
The syntax, for MJ programs, is given in Figure 1. An MJ program is thus a collection of
class definitions plus a sequence of statements, s, to be evaluated. This sequence corresponds
to the body of the main method in a Java program [9, §12.1.4].
For example, here are some typical MJ class definitions.
1
Another approach for lazy functional programming with state is the use of monads [21]. Wadler has shown
that effects systems can easily be adapted to monads [22].
4
Program
p ::= cd 1 . . . cd n ; s
Class definition
cd ::= class C extends C
{fd 1 . . . fd k
cnd
md 1 . . . md n }
Field definition
fd ::= C f ;
Constructor definition
cnd ::= C(C1 x1 , . . . , Cj xj ){super(e1 , . . . , ek ); s1 . . . sn }
Method definition
md ::= τ m(C1 x1 , . . . , Cn xn ){s1 . . . sk }
Return type
τ ::= C|void
Expression
e ::= x
|
null
|
e.f
|
(C)e
|
pe
Promotable expression
pe ::= e.m(e1 , . . . , ek )
|
new C(e1 , . . . , ek )
Statement
s ::= ;
|
pe;
|
if (e == e){s1 . . . sk } else {sk+1 . . . sn }
|
e.f = e;
|
C x;
|
x = e;
|
return e;
|
{s1 . . . sn }
Variable
Null
Field access
Cast
Promotable expression
Method invocation
Object creation
No-op
Promoted expression
Conditional
Field assignment
Local variable declaration
Variable assignment
Return
Block
Figure 1: Syntax for MJ programs
class Cell extends Object{
Object contents;
Cell (Object start){
super();
this.contents = start;
};
void set(Object update){
this.contents = update;
};
}
class Recell extends Cell{
Object undo;
Recell (Object start){
super(start);
this.undo = null;
};
void set(Object update){
this.undo = this.contents;
this.contents = update;
};}
This code defines two classes: Cell which is a subclass of the Object class, and Recell
which is a subclass of Cell. The Cell class has one field, contents. The constructor method
simply assigns the field to the value of the parameter. The Cell class defines a method set,
which sets the field to a given value.
Recell objects inherit the contents field from their superclass, and also have another
5
field, undo. The constructor method first calls the superclass constructor method (which will
assign the contents field) and then sets the undo field to the null object. The Recell class
definition overrides the set method of its superclass.
As with Featherweight Java, we will insist on a certain amount of syntactic regularity in
class definitions, although this is really just to make the definitions compact. We insist that
all class definitions (1) include a supertype (we assume a distinguished class Object); (2)
include a constructor method (currently we only allow a single constructor method per class);
(3) have a call to super as the first statement in a constructor method; (4) have a return at
the end of every method definition except for void methods (this constraint is enforced by
the type system); and (5) write out field accesses explicitly, even when the receiver is this.
In what follows, again for compactness, we assume that MJ programs are well-formed,
i.e. we insist that (1) they do not have duplicate definitions for classes, fields and methods
(currently we do not allow overloaded methods for simplicity—as overloading is determined
statically, overloaded methods can be simulated faithfully in MJ); (2) fields are not redefined in
subclasses (we do not allow shadowing of fields); and (3) there are no cyclic class definitions
(for example A extends B and B extends A). We do not formalise these straightforward
conditions.
A class definition contains a collection of field and method definitions, and a single constructor definition. A field is defined by a type and a name. Methods are defined as a return
type, a method name, an ordered list of arguments, where an argument is a variable name
and type, and a body. A constructor is defined by the class name, an ordered list of arguments and a body. There are a number of well-formedness conditions for a collection of class
definitions which are formalised in the next section.
The rest of the definition, in Figure 1, defines MJ expressions and statements. We assume
a number of metavariables: f ranges over field names, m over method names, and x over
variables. We assume that the set of variables includes a distinguished variable, this, which
is not permitted to occur as the name of an argument to a method or on the left of an
assignment. In what follows, we shall find it convenient to write e to denote the possibly
empty sequence e0 , . . . , en (and similarly for C, x, etc.). We write s to denote the sequence
s1 . . . sn with no commas (and similarly for fd and md ). We abbreviate operations on pairs of
sequences in the obvious way, thus for example we write C x for the sequence C1 x1 , . . . , Cn xn
where n is the length of C and x.
The reader will note MJ has two classes of expressions: the class of ‘promotable expressions’, pe, defines expressions that can be can be promoted to statements by postfixing a
semicolon ‘;’; the other, e, defines the other expression forms. This slightly awkward division
is imposed upon us by our desire to make MJ a valid fragment of Java. Java [9, §14.8] only
allows particular expression forms to be promoted to statements by postfixing a semicolon.
This leads to some rather strange syntactic surprises: for example, x++; is a valid statement,
but (x++); is not!
MJ includes the essential imperative features of Java. Thus we have fields, which can
be both accessed and assigned to, as well as variables, which can be locally declared and
assigned to. As with Java, MJ supports block-structure; consider the following valid MJ code
fragment.
if(var1 == var2) { ; }
else {
Object temp;
6
temp = var1;
var1 = var2;
var2 = temp;
}
This code compares two variables, var1 and var2. If they are not equal then it creates a
new locally scoped variable, temp, and uses this to swap the values of the two variables. At
the end of the block, temp will no longer be in scope and will be removed from the variable
stack.
2.2
Types
As with FJ, for simplicity, MJ does not have primitive types, sometimes called base types.
Thus all well-typed expressions are of a class type, C. All well-typed statements are of type
void, except for the statement form return e; which has the type of e (i.e. a class type). We
use τ to range over valid statement types. The type of a method is a pair, written C → τ ,
where C is a sequence of argument types and τ is the return type (if a method does not return
anything, its return type is void). We use µ to range over method types.
Expression types
C
a valid class name, including a distinguished class Object
Statement types
τ ::= void|C
Method types
µ ::= C1 , . . . , Cn → τ
Java, and MJ, class definitions contain both typing information and code. This typing
information is extracted and used to typecheck the code. Thus before presenting the typechecking rules we need to specify how this typing information is induced by MJ code.
This typing information consists of two parts: The subclassing relation, and a class table
(which stores the types associated with classes). First let us consider the subclassing relation.
Recall that in MJ we restrict class declarations so that they must give the name of class that
they are extending, even if this class is the Object class.
A well-formed program, p, then induces an immediate subclassing relation, which we
write ≺1 . We define the subclassing relation, ≺, as the reflexive, transitive closure of this
immediate subclassing relation. This can be defined formally as follows.
[TR-Immediate]
class C1 extends C2 {. . .} ∈ p
C1 ≺ 1 C2
[TR-Extends]
C1 ≺ 1 C2
C1 ≺ C 2
[TR-Transitive]
C1 ≺ C 2
C2 ≺ C 3
C1 ≺ C 3
[TR-Reflexive]
C≺C
A well-formed program p also induces a class table, ∆p . As the program is fixed, we will
simply write this as ∆. A class table, ∆, is actually a triple, (∆m , ∆c , ∆f ), which provides
typing information about the methods, constructors, and fields, respectively. ∆ m is a partial
map from a class name to a partial map from a method name to that method’s type. Thus
7
∆m (C)(m) is intended to denote the type of method m in class C. ∆c is a partial map from
a class name to the type of that class’s constructor method. ∆f is a partial map from a class
name to a map from a field name to a type. Thus ∆f (C)(f ) is intended to denote the type
of f in class C. The details of how a well-formed program p induces a class table ∆ are given
below.
Method Type
def
∆m (C)(m) =
(
C → C 00
0
∆m (C )(m)
where md i = C 00 m(C x){. . .}, for some i, 1 ≤ i ≤ n
where m ∈
/ md 1 . . . md n
where class C extends C 0 {fd cnd md 1 . . . md n } ∈ p
Constructor Type
def
∆c (C) = C1 , . . . , Cj
where class C extends C 0 {fd cnd md } ∈ p
and cnd = C(C1 x1 , . . . , Cj xj ){. . .}
Field Type
def
∆f (C)(f ) =
(
C 00
∆f (C 0 )(f )
where fd i = C 00 f, for some i, 1 ≤ i ≤ k and ∆f (C 0 )(f ) ↑
otherwise
where class C extends C 0 {fd 1 . . . fd k cnd md } ∈ p
Up to now we have assumed that the class definitions are well-formed. Now let us define
formally well-formedness of class definitions. First we will find it useful to define when a type
(either a method type or statement type) is well-formed with respect to a class table. This is
written as a judgement ∆ ` µ ok which means that µ is a valid type given the class table ∆.
We overload this judgement form for statement types. (We define dom(∆) to be the domain
of ∆f , ∆m or ∆c , which are all equal.)
[T-CType]
[T-VType]
[T-MType]
∆ ` C ok
where C ∈ dom(∆)
∆ ` void ok
∆ ` C1 ok
...
∆ ` Cn ok
∆ ` τ ok
∆ ` C1 , . . . , Cn → τ ok
Now we define formally the judgement for well-formed class tables, which is written ` ∆ ok.
This essentially checks two things: firstly that all types are valid given the classes defined in
the class table, and secondly that if a method is overridden then it is so at the same type.
The judgements are as follows.
8
[T-FieldsOk]
∆ ` ∆f (C)(f1 ) ok
...
∆ ` ∆f (C)(fn ) ok
∆ ` ∆f (C) ok
[T-ConsOK]
∆ ` C1 ok
...
∆ ` Cn ok
∆ ` ∆c (C) ok
where dom(∆f (C)) = {f1 , . . . , fn }
where ∆c (C) = C1 , . . . , Cn
where
∆m (C)(m) = µ
C ≺1 C 0
∆m (C 0 )(m) = µ0
µ = µ0
where
∆m (C)(m) = µ
C ≺1 C 0
m 6∈ dom(∆m (C 0 ))
∆ ` µ ok
[T-MethOk1]
∆ ` C.m ok
[T-MethOk2]
[T-MethsOk]
[T-ClassOK]
∆ ` µ ok
∆ ` C.m ok
∆ ` C.m1 ok . . . ∆ ` C.mn ok
∆ ` ∆m (C) ok
∆ ` ∆f (C) ok
∆ ` ∆m (C) ok
` ∆ ok
∆ ` ∆c (C) ok
where dom(∆m (C)) = {m1 , . . . , mn }
∀C ∈ dom(∆)
We can now define the typing rules for expressions and promotable expressions. Our
treatment of casting is the same as in FJ—thus we include a case for ‘stupid’ casting, where
the target class is completely unrelated to the subject class. This is needed to handle the
case where an expression without stupid casts may reduce to one containing a stupid cast.
Consider the following expression, taken from [12], where classes A and B are both defined to
be subclasses of the Object class, but are otherwise unrelated.
(A)(Object)new B()
According to the Java Language Specification [9, §15.16], this is well-typed, but consider
its operational behaviour: a B object is created and is dynamically upcast to Object (which
has no dynamic effect). At this point we wish to downcast the B object to A—a ‘stupid’ cast!
Thus if we wish to maintain a subject reduction theorem (that the result of a single step
reduction of a well-typed program is also a well-typed program) we need to include the [TEStupidCast] rule. For the same reason, we also require two rules for typing an if statement.
Java [9, §15.21.2] enforces statically that when comparing objects, one object should be a
subclass of the other. However this is not preserved by the dynamics. Consider again the
unrelated classes A and B. The following code fragment is well-typed but at runtime will end
up comparing objects of unrelated classes.
if ((Object)new A() == (Object)new D()) { ... } else {...};
The reader should note that a valid Java/MJ program is one that does not have occurrences of [TE-StupidCast] or [TS-StupidIf] in its typing derivation.
A typing environment, Γ, is a map from program variables to expression types. We write
Γ, x : C to denote the map Γ extended so that x maps to C. We write ∆ ` Γ ok to mean
9
that the types in the codomain of the typing environment are well-formed with respect to the
class table, ∆.
A typing judgement for a given MJ expression, e, is written ∆; Γ ` e : C where ∆ is a class
table and Γ is a typing environment. These rules are given below and are fairly intuitive except
perhaps [TE-Null], which allows the null value to have any valid type (see [9, §4.1]). One
interesting fact about the evaluation of null is (C)(Object)(B)e will only reduce, without
generating an exception, if e evaluates to null, assuming B is not a subclass of C.
[TE-Var]
[TE-Null]
∆ ` C ok
∆ ` Γ ok
` ∆ ok
∆; Γ ` null : C
[TE-FieldAccess]
[TE-UpCast]
[TE-DownCast]
[TE-StupidCast]
∆ ` Γ ok
` ∆ ok
∆; Γ, x : C ` x : C
∆; Γ ` e : C2
∆f (C2 )(f ) = C1
∆; Γ ` e.f : C1
∆; Γ ` e : C2
C2 ≺ C 1
∆ ` C1
∆; Γ ` (C1 )e : C1
∆; Γ ` e : C2
C1 ≺ C 2
∆ ` C1
∆; Γ ` (C1 )e : C1
∆; Γ ` e : C2
C2 ⊀ C 1
C1 ⊀ C 2
∆; Γ ` (C1 )e : C1
∆ ` C1
A typing judgement for a given promotable expression, pe, is also written ∆; Γ ` pe : C
where ∆ is a class table and Γ is a typing environment. The rules for forming these judgements
are as follows.
∆; Γ ` e : C 0
∆; Γ ` e1 : C1
...
∆; Γ ` en : Cn
∆m (C 0 )(m) = C10 , . . . , Cn0 → C
C1 ≺ C10 . . . Cn ≺ Cn0
[TE-Method]
∆; Γ ` e.m(e1 , . . . , en ) : C
∆; Γ ` e1 : C10
...
∆; Γ ` en : Cn0
∆c (C) = C1 , . . . , Cn
C10 ≺ C1 . . . Cn0 ≺ Cn
[TE-New]
∆; Γ ` new C(e1 , . . . , en ) : C
A typing judgement for a statement, s, is written ∆; Γ ` s : τ where ∆ is a class table and
Γ is a typing environment. As we noted earlier, statements in Java can have a non-void type
(see rule [TS-Return]). The rules for forming these typing judgements are as follows.
10
` ∆ ok
∆ ` Γ ok
∆; Γ `; : void
[TS-NoOp]
∆; Γ ` pe : τ
∆; Γ ` pe; : void
[TS-PE]
∆; Γ ` s1 : void
∆; Γ ` s2 : void
∆; Γ ` e1 : C 0
∆; Γ ` e2 : C 00
[TS-If]
∆; Γ ` if (e1 == e2 ){s1 } else {s2 } : void
where C 00 ≺ C 0 ∨ C 0 ≺ C 00
∆; Γ ` s2 : void
∆; Γ ` s1 : void
∆; Γ ` e1 : C 0
∆; Γ ` e2 : C 00
[TS-StupidIf]
∆; Γ ` if (e1 == e2 ){s1 } else {s2 } : void
where C 00 ⊀ C 0 ∧ C 0 ⊀ C 00
∆; Γ ` e1 : C1
C2 ≺ C 3
∆; Γ ` e2 : C2
∆f (C1 )(f ) = C3
[TS-FieldWrite]
∆; Γ ` e1 .f = e2 ; : void
[TS-VarWrite]
∆; Γ ` x : C
[TS-Return]
[TS-Block]
∆; Γ ` e : C 0
C0 ≺ C
∆; Γ ` x = e; : void
x 6= this
∆; Γ ` e : C
∆; Γ ` return e; : C
∆; Γ ` s1 . . . sn : void
∆; Γ ` {s1 . . . sn } : void
Java allows variable declarations to occur at any point in a block. To handle this we
introduce two typing rules for sequencing: one for the case where the first statement in the
sequence is a variable declaration, and one for the other cases. An alternative approach
would be to force each statement to return the new typing environment. We feel that our
presentation is simpler. Typing judgements for a sequence of statements, s, is written ∆; Γ `
s : τ where ∆ is a class table and Γ is a typing environment. The rules for forming these
judgements are given below.
[TS-Intro]
[TS-Seq]
∆; Γ, x : C ` s1 . . . sn : τ
∆; Γ ` C x; s1 . . . sn : τ
∆; Γ ` s1 : void
∆; Γ ` s2 . . . sn : τ
∆; Γ ` s1 s2 . . . sn : τ
s1 6= C x;
Finally we define the typing of the super call in the constructor of a class. A call to the
empty constructor in a class that directly extends Object is always valid, and otherwise it
must be a valid call to the constructor of the parents class. The expressions evaluated before
this call are not allowed to reference this [9, §8.8.5.1]. This is because it is not a valid object
until the parents constructor has been called.
11
[T-CObject]
C ≺1 Object
∆; Γ, this : C ` super() : void
...
∆; Γ0 ` en : Cn0
∆; Γ0 ` e1 : C10
[T-CSuper]
∆; Γ ` super(e1 , . . . , en ); : void
where Γ(this) = C, Γ = Γ0 ] {this : C}
and ∆c (C 0 ) = C1 , . . . , Cn
and C ≺1 C 0 and
C10 ≺ C1 . . . Cn0 ≺ Cn
Before we give the typing rules involving methods we first define some useful auxiliary
functions for accessing the bodies of constructors and methods.
Method Body
def
mbody(C, m) =
(
where md i = C 00 m(Cx){s}
(x, s)
mbody(C, m0 ) where m ∈
/ md 1 . . . md n
where class C extends C 0 {f d cnd md 1 . . . md n } ∈ p
Constructor Body
def
cnbody(C) = (x, s)
where class C extends C 0 {fd C(C 00 x){s} md } ∈ p
We can now formalise the notion of a program being well-typed with respect to its class
table. This is denoted by the judgement ∆ ` p ok. Informally this involves checking that
each method body and constructor body is well-typed and that the type deduces matches
that contained in ∆.
We introduce two new judgement forms: ∆ ` C mok denotes that the methods of class C
are well-typed, and ∆ ` C cok denotes that the constructor method of class C is well-typed.
The rules for forming these judgements are given below.
[T-MDefn]
[T-Mbodys]
∆; Γ ` s : τ
∆ ` mbody(C, m) ok
where Γ = {this : C, x : C}
and mbody(C, m) = (x, s)
and ∆m (C)(m) = (C → τ )
∆ ` mbody(C, m1 ) ok
...
∆ ` mbody(C, mn ) ok
∆ ` C mok
where
dom(∆m (C)) = {m1 , . . . , mn }
∆; Γ ` super(e); : void
∆; Γ ` s : void
∆ ` C cok
where Γ = this : C, x : C
and ∆c (C) = C and
cnbody(C) = (x, super(e); s)
[T-CDefn]
[T-ProgDef]
∆ ` C1 cok
∆ ` C1 mok
∆ ` Cn cok
...
∆ ` Cn mok
∆ ` p ok
12
where dom(∆) = {C1 , . . . , Cn }
2.3
Operational Semantics
We define the operational semantics of MJ in terms of transitions between configurations,
rather than using Felleisen-style evaluation contexts. As the reader will see this style of
semantics has the advantage that the transition rules are defined by case analysis rather than
by induction, which simplifies some proofs.
A configuration is a four-tuple, containing the following information:
1. Heap: A finite partial function that maps oids to heap objects, where a heap object is
a pair of a class name and a field function. A field function is a partial finite map from
field names to values.
2. Variable Stack: This essentially maps variable names to oids. To handle static blockstructured scoping it is implemented as a list of lists of partial functions from variables to
values. (This is explained in more detail later.) We use ◦ to denote stack concatenation.
3. Term: The closed frame to be evaluated.
4. Frame stack: This is essentially the program context in which the term is currently
being evaluated.
This can be defined formally as follows.
Configuration
config ::= (H, VS , CF , FS )
Frame Stack
FS ::= F ◦ FS |[]
Frame
F ::= CF |OF
Closed frame
CF ::= s|return e; |{ }|e|super(e)
Open frame
OF ::= if (• == e){s1 } else {s2 };
|
if (v == •){s1 } else {s2 };
|
•.f | • .f = e; |v.f = •; |(C)•
|
v.m(v1 , . . . , vi−1 , •, ei+1 , . . . , en )
|
new C(v1 , . . . , vi−1 , •, ei+1 , . . . , en )
|
super(v1 , . . . , vi−1 , •, ei+1 , . . . , en )
|
x = •; |return •; | • .m(e)
Values
v ::= null|o
Variable Stack
VS ::= MS ◦ VS |[]
Method Scope
MS ::= BS ◦ MS |[]
Block Scope
BS ::= is a finite partial function from
variables to pairs of expression
types and values
Heap
H ::= is a finite partial function from oids
to heap objects
Heap Objects
ho ::= (C, F)
F ::= is a finite partial function from
field names to values
CF is a closed frame (i.e. with no hole) and OF is an open frame (i.e. requires an
expression to be substituted in for the hole).
The structure for representing the variable scopes may seem complicated but they are
required to correctly model the block structure scoping of Java. The following example
highlights this. The left hand side gives the source code and the right the contents of the
variable stack.
13
1
2
3
4
5
6
7
8
9
10
11
←− VS
B m(A a) {
←− ({a 7→ (A, v1 )} ◦ []) ◦ VS
B r;
←− ({r 7→ (B, null), a 7→ (A, v1 )} ◦ []) ◦ VS
if(this.x == arg) {
r = arg;
} else {
←− ({} ◦ {r 7→ (B, null), a 7→ (A, v1 )} ◦ []) ◦ VS
A t;
←− ({t 7→ (A, null)} ◦ {r 7→ (B, null), a 7→ (A, v1 )} ◦ []) ◦ VS
t = this.getVal();
←− ({t 7→ (A, v3 } ◦ {r 7→ (B, null), a 7→ (A, v2 )} ◦ []) ◦ VS
r = this.create(t,t);
←− ({t 7→ (A, v3 } ◦ {r 7→ (B, v4 ), a 7→ (A, v2 )} ◦ []) ◦ VS
}
←− ({r 7→ (B, v4 ), a 7→ (A, v2 )} ◦ []) ◦ VS
return r;
}
←− VS
Before calling this method, let us assume we have a variable scope, VS . A method call
should not affect any variables in the current scopes, so we create a new method scope,
{a 7→ (A, v)} ◦ [], on entry to the method. This scope consists of a single block scope that
points the argument a at the value v with a type annotation of A. On line 2 the block scope
is extended to contain the new variable r. On line 5 we assumed that this.x 6= arg and enter
into a new block. This has the effect to adding a new empty block scope, {}, into the current
method scope. On line 6 this new scope is extended to contain the variable t. Notice how it
is added to the current outermost scope, as was the variable r. Updates can however occur
to block scopes anywhere in the current method scope. This can be seen on line 8 where r is
updated from the outer scope. On line 9 the block scope is disposed of, and hence t can not
be accessed by the statement on line 10.
We find it useful to define two operations on method scopes, in addition to the usual list
operations. The first, eval(MS , x), evaluates a variable, x, in a method scope, MS . This
is a partial function and is only defined if the variable name is in the scope. The second,
update(MS , x 7→ v), updates a method scope MS with the value v for the variable x. Again
this is a partial function and is undefined if the variable is not in the scope.
2.3.1
eval ((BS ◦ MS ), x)
def
update((BS ◦ MS ), (x 7→ v))
def
=
=
(
BS (x)
where x ∈ dom(BS )
eval (MS , x) otherwise
(
BS [x 7→ (v, C)] ◦ MS
where BS (x) = (v 0 , C)
BS ◦ update(MS , (x 7→ v)) otherwise
Reductions
This section defines the transition rules that correspond to meaningful computation steps.
In spite of the computational complexity of MJ, there are only seventeen rules, one for each
syntactic constructor.
We begin by giving the transition rules for accessing, assigning values to, and declaring
variables. Notice that the side condition in the [E-VarWrite] rule ensures that we can only
write to variables declared in the current method scope. The [E-VarIntro] rule follows Java’s
restriction that a variable declaration can not hide an earlier declaration within the current
method scope.2 (Note also how the rule defines the binding for the new variable in the current
2
This sort of variable hiding is, in contrast, common in functional languages such as SML.
14
block scope.)
[E-VarAccess]
(H, MS ◦ VS , x, FS ) → (H, MS ◦ VS , v, FS )
[E-VarWrite]
(H, MS ◦ VS , x = v; , FS )
→ (H, (update(MS , (x 7→ v))) ◦ VS , ; , FS )
where eval(MS , x) ↓
(H, (BS ◦ MS ) ◦ VS , C x; , FS ) →
(H, (BS 0 ◦ MS ) ◦ VS , ; , FS )
where x ∈
/ dom(BS ◦ MS )
and BS 0 = BS [x 7→ (null, C)]
[E-VarIntro]
where eval(MS , x) = (v, C)
Now we consider the rules for constructing and removing scopes. The first rule [EBlockIntro] introduces a new block scope, and leaves a ‘marker’ token on the frame stack.
The second [E-BlockElim] removes the token and the outermost block scope. The final rule
[E-Return] leaves the scope of a method, by removing the top scope, MS .
[E-BlockIntro]
(H, MS ◦ VS , {s}, FS ) → (H, ({} ◦ MS ) ◦ VS , s, ({ }) ◦ FS ))
[E-BlockElim]
(H, (BS ◦ MS ) ◦ VS , { }, FS ) → (H, MS ◦ VS , ; , FS )
[E-Return]
(H, MS ◦ VS , return v; , FS ) → (H, VS , v, FS )
Next we give the transition rules for the conditional expression. One should note that the
resulting term of the transition is a block.
[E-If]
(H, VS , (if (v1 == v2 ){s1 } else {s2 }; ), FS )→ (H, VS , {s1 }, FS )
→ (H, VS , {s2 }, FS )
if v1 = v2
if v1 =
6 v2
Finally we consider the rules dealing with objects. First let us define the transition rule
dealing with field access and assignment, as they are reasonably straightforward.
[E-FieldAccess]
(H, VS , o.f, FS ) → (H, VS , v, FS )
[E-FieldWrite]
(H, VS , o.f = v; , FS ) → (H 0 , VS , ; , FS )
where o ∈ dom(H) and H(o) = (C, F)
and F(f ) = v and ∆f (C)(f ) = C 0
where H(o) = (C, F), f ∈ dom(F),
∆f (C)(f ) = C 0
H 0 = H[o 7→ (C, F0 )]
Now we consider the rules dealing with casting of objects. Rule [E-Cast] simply ensures
that the cast is valid (if it is not, the program should enter an error state—this is covered in
§2.3.2). Rule [E-NullCast] simply ignores any cast of a null object.
[E-Cast]
(H, VS , ((C2 )o), FS ) → (H, VS , o, FS )
[E-NullCast]
(H, VS , ((C)null), FS ) → (H, VS , null, FS )
15
where H(o) = (C1 , F)
and C1 ≺ C2
Let us consider the transition rule involving the creation of objects. The [E-New] rule
creates a fresh oid, o, and places on the heap the heap object consisting of the class C and
assigns all the fields to null. As we are executing a new method, a new method scope is
created and added on to the variable stack. This method scope initially consists of just one
block scope, that consists of bindings for the method parameters, and also a binding for the
this identifier. The method body B is then the next term to be executed, but importantly
the continuation return o; is placed on the frame stack. This is because the result of this
statement is the oid of the object, and the method scope is removed.
[E-New]
(H, VS , new C(v), FS ) →
(H[o 7→ (C, F)], (BS ◦ []) ◦ VS , s, (return o; ) ◦ FS )
where cnbody(C) = (x, s)
∆c (C) = C, o ∈
/ dom(H)
F = {f 7→ null} ∀f ∈ fields(C)
BS = {this 7→ (o, C), x 7→ (v, C)}
Next we consider the transition rule for the super statement, which occurs inside constructor methods.
(H, MS ◦ VS , super(v), FS ) →
(H, (BS 0 ◦ []) ◦ (MS ◦ VS ), s, (return o; ) ◦ FS )
[E-Super]
where MS (this) = (o, C), C ≺1 C 0
BS 0 = {this 7→ (o, C 0 ), x 7→ (v, C)}
∆c (C) = C, cnbody(C 0 ) = (x, s)
Next we can give the transition rule for method invocation. Invocation is relatively
straightforward: note how a new method scope is created, consisting of just the bindings
for the method parameters and the this identifier. We require two rules as a method returning a void type requires an addition to the stack to clear the new method scope once the
method has completed. Recall that in [E-Method] rule, the last statement in the sequence s
will be a return if the method is well typed.
[E-Method]
(H, VS , o.m(v), FS ) →
(H, (BS ◦ []) ◦ VS , s, FS )
[E-MethodVoid]
(H, VS , o.m(v), FS ) →
(H, (BS ◦ []) ◦ VS , s, (return o; ) ◦ FS )
where mbody(C, m) = (x, s)
H(o) = (C, F), ∆m (C)(m) = C → C 0
BS = {this 7→ (o, C), x 7→ (v, C)}
where H(o) = (C, F)
∆m (C)(m) = C → void
mbody(C, m) = (x, s)
BS = {this 7→ (o, C), x 7→ (v, C)}
Finally we have two reductions rules for fully reduced terms. The first rule deals with
completed statements, and the second for evaluated expressions.
[E-Skip]
(H, VS , ; , F ◦ FS ) → (H, VS , F, FS )
[E-Sub]
(H, VS , v, F ◦ FS ) → (H, VS , F [v] ◦ FS )
To assist the reader, all the reduction rules are repeated in full in Figure 2. There are
16
a number of other reduction rules, that simply decompose terms. These rules essentially
embody the order of evaluation, and are given in Figure 3.
2.3.2
Error states
A number of expressions will lead us to a predictable error state. These are errors that are
allowed at run-time as they are dynamically checked for by the Java Virtual Machine. Java’s
type system is not capable of removing these errors statically. The two errors that can be
generated, in MJ, are NullPointerException, written here NPE, and ClassCastException,
CCE.
[E-NullField]
[E-NullWrite]
[E-NullMethod]
[E-InvCast]
(H, VS , null.f, FS ) → NPE
(H, VS , null.f = v, FS ) → NPE
(H, VS , null.m(v1 , . . . , vn ), FS ) → NPE
(H, VS , (C)o, FS ) → CCE
where H(o) = (C 0 , F) and C 0 ⊀ C
Definition 2.1 (Terminal configuration) A configuration is said to be terminal if it is
a valid error (NPE or CCE) or it is of the form (H, VS , v, []).
2.3.3
Example Execution
To help the reader understand our operational semantics, in this section we will consider a
simple code fragment and see how the operational semantics captures its dynamic behaviour.
Consider the following MJ code whose effect is to swap the contents of two variables var1
and var2, using a temporary variable temp.
if(var1 == var2) {
;
} else {
Object temp;
temp = var1;
var1 = var2;
var2 = temp;
}
We consider its execution in a configuration where MS maps var1 to a value, say v1, and
var2 to a value, say v2.
→
→
→
→
→
→
(H, MS
(H, MS
(H, MS
(H, MS
(H, MS
(H, MS
(H, MS
◦ VS , if (var1 == var2){; } else {. . .}, FS )
◦ VS , var1, (if (• == var2){; } else {. . .}) ◦ FS )
◦ VS , v1, (if (• == var2){; } else {. . .}) ◦ FS )
◦ VS , (if (v1 == var2){; } else {. . .}), FS )
◦ VS , var2, (if (v1 == •){; } else {. . .}) ◦ FS )
◦ VS , v2, (if (v1 == •){; } else {. . .}) ◦ FS )
◦ VS , (if (v1 == v2){; } else {. . .}), FS )
At this point there are two possibilities: we will consider the case where v1 6= v2.
17
[E-VarAccess]
(H, MS ◦ VS , x, FS ) → (H, MS ◦ VS , v, FS )
[E-VarWrite]
(H, MS ◦ VS , x = v; , FS )
→ (H, (update(MS , (x 7→ v))) ◦ VS , ; , FS )
where eval(MS , x) ↓
[E-VarIntro]
(H, (BS ◦ MS ) ◦ VS , C x; , FS ) →
(H, (BS 0 ◦ MS ) ◦ VS , ; , FS )
where x ∈
/ dom(BS ◦ MS )
and BS 0 = BS [x 7→ (null, C)]
[E-BlockIntro]
(H, MS ◦ VS , {s}, FS )
→ (H, ({} ◦ MS ) ◦ VS , s, ({ }) ◦ FS ))
[E-BlockElim]
(H, (BS ◦ MS ) ◦ VS , { }, FS ) → (H, MS ◦ VS , ; , FS )
[E-Return]
(H, MS ◦ VS , return v; , FS ) → (H, VS , v, FS )
[E-If]
(H, VS , (if (v1 == v2 ){s1 } else {s2 }; ), FS )
→ (H, VS , {s1 }, FS )
[E-If2]
(H, VS , (if (v1 == v2 ){s1 } else {s2 }; ), FS )
→ (H, VS , {s2 }, FS )
where eval(MS , x) = (v, C)
if v1 = v2
if v1 6= v2
where o ∈ dom(H) and H(o) = (C, F)
and F(f ) = v and ∆f (C)(f ) = C 0
where H(o) = (C, F), f ∈ dom(F),
∆f (C)(f ) = C 0
H 0 = H[o 7→ (C, F0 )]
[E-FieldAccess]
(H, VS , o.f, FS ) → (H, VS , v, FS )
[E-FieldWrite]
(H, VS , o.f = v; , FS ) → (H 0 , VS , ; , FS )
[E-Cast]
(H, VS , ((C2 )o), FS ) → (H, VS , o, FS )
[E-NullCast]
(H, VS , ((C)null), FS ) → (H, VS , null, FS )
[E-New]
(H, VS , new C(v), FS ) →
where cnbody(C) = (x, s)
/ dom(H)
(H[o 7→ (C, F)], (BS ◦ []) ◦ VS , s, (return o; ) ◦ FS )∆c (C) = C, o ∈
F = {f 7→ null} ∀f ∈ fields(C)
BS = {this 7→ (o, C), x 7→ (v, C)}
[E-Super]
(H, MS ◦ VS , super(v), FS ) →
(H, (BS 0 ◦ []) ◦ (MS ◦ VS ), s, (return o; ) ◦ FS )
where MS (this) = (o, C), C ≺1 C 0
BS 0 = {this 7→ (o, C 0 ), x 7→ (v, C)}
∆c (C) = C, cnbody(C 0 ) = (x, s)
[E-Method]
(H, VS , o.m(v), FS ) →
(H, (BS ◦ []) ◦ VS , s, FS )
[E-MethodVoid]
(H, VS , o.m(v), FS ) →
(H, (BS ◦ []) ◦ VS , s, (return o; ) ◦ FS )
where mbody(C, m) = (x, s)
H(o) = (C, F), ∆m (C)(m) = C → C 0
BS = {this 7→ (o, C), x 7→ (v, C)}
where H(o) = (C, F)
∆m (C)(m) = C → void
mbody(C, m) = (x, s)
BS = {this 7→ (o, C), x 7→ (v, C)}
[E-Skip]
(H, VS , ; , F ◦ FS ) → (H, VS , F, FS )
[E-Sub]
(H, VS , v, F ◦ FS ) → (H, VS , F [v] ◦ FS )
Figure 2: MJ reduction rules
18
where H(o) = (C1 , F)
and C1 ≺ C2
[EC-Seq]
[EC-Return]
[EC-ExpState]
(H, VS , s1 s2 . . . sn , FS ) → (H, VS , s1 , (s2 . . . sn ) ◦ FS )
(H, MS ◦ VS , return e; , FS )
→ (H, MS ◦ VS , e, (return •; ) ◦ FS )
(H, VS , e0 ; , FS ) → (H, VS , e0 , FS )
[EC-If1]
(H, VS , if (e1 == e2 ){s1 } else {s2 }; , FS )
→ (H, VS , e1 , (if (• == e2 ){s1 } else {s2 }; ) ◦ FS )
[EC-If2]
(H, VS , if (v1 == e2 ){s1 } else {s2 }; , FS )
→ (H, VS , e2 , (if (v1 == •){s1 } else {s2 }; ) ◦ FS )
[EC-FieldAccess]
(H, VS , e.f, FS ) → (H, VS , e, (•.f ) ◦ FS )
[EC-Cast]
(H, VS , (C)e, FS ) → (H, VS , e, ((C)•) ◦ FS )
[EC-FieldWrite1]
(H, VS , e1 .f = e2 ; , FS ) → (H, VS , e1 , (•.f = e2 ; ) ◦ FS )
[EC-FieldWrite2]
(H, VS , v1 .f = e2 ; , FS ) → (H, VS , e2 , (v1 .f = •; ) ◦ FS )
[EC-VarWrite]
(H, VS , x = e; , FS ) → (H, VS , e, (x = •; ) ◦ FS )
[EC-New]
(H, VS , new C(v1 , . . . , vi−1 , ei , . . . en ), FS )
→ (H, VS , ei , (new C(v1 , . . . , vi−1 , •, . . . en )) ◦ FS )
[EC-Super]
(H, VS , super(v1 , . . . , vi−1 , ei , . . . en ), FS )
→ (H, VS , ei , (super(v1 , . . . , vi−1 , •, . . . en )) ◦ FS )
[EC-Method1]
[EC-Method2]
(H, VS , e.m(e1 , . . . , en ), FS ) → (H, e, (•.m(e1 , . . . , en )) ◦ FS )
(H, VS , v.m(v1 , . . . , vi−1 , ei , . . . en ), FS )
→ (H, VS , ei , (v.m(v1 , . . . , vi−1 , •, . . . en )) ◦ FS )
Figure 3: MJ decomposition reduction rules
19
→
→
→
→
→
→
→
→
(H, ({}) ◦ MS ◦ VS , o temp; . . . , ({}) ◦ FS )
(H, ({}) ◦ MS ◦ VS , o temp; , (temp = var1; . . .) ◦ ({}) ◦ FS )
(H, ({temp 7→ (null, o}) ◦ MS ◦ VS , ; , (temp = var1; . . .) ◦ ({}) ◦ FS )
(H, ({temp 7→ (null, o)}) ◦ MS ◦ VS , (temp = var1; . . .), ({}) ◦ FS )
(H, ({temp 7→ (null, o)}) ◦ MS ◦ VS , temp = var1; , (var1 = var2; . . .) ◦ ({}) ◦ FS )
(H, ({temp 7→ (null, o)}) ◦ MS ◦ VS , var1, (temp = •; ) ◦ (var1 = var2; . . .) ◦ ({}) ◦ FS )
(H, ({temp 7→ (null, o)}) ◦ MS ◦ VS , v1, (temp = •; ) ◦ (var1 = var2; . . .) ◦ ({}) ◦ FS )
(H, ({temp 7→ (null, o)}) ◦ MS ◦ VS , temp = v1; , (var1 = var2; . . .) ◦ ({}) ◦ FS )
At this point we update the variable stack; note how the update does not change the type.
→
→
→
→
→
→
(H, ({temp 7→ (v1, o)}) ◦ MS
(H, ({temp 7→ (v1, o)}) ◦ MS
(H, ({temp 7→ (v1, o)}) ◦ MS
(H, ({temp 7→ (v1, o)}) ◦ MS
(H, ({temp 7→ (v1, o)}) ◦ MS
(H, ({temp 7→ (v1, o)}) ◦ MS
◦ VS , ; , (var1 = var2; . . .) ◦ ({}) ◦ FS )
◦ VS , var1 = var2; . . . , ({}) ◦ FS )
◦ VS , var1 = var2; , (var2 = temp; ) ◦ ({}) ◦ FS )
◦ VS , var2, var1 = •; ◦(var2 = temp; ) ◦ ({}) ◦ FS )
◦ VS , v2, var1 = •; ◦(var2 = temp; ) ◦ ({}) ◦ FS )
◦ VS , var1 = v2; , (var2 = temp; ) ◦ ({}) ◦ FS )
Let MS 0 be a variable scope which is the same as MS except that var1 is mapped to v2
instead of v1.
→
→
→
→
→
(H, ({temp 7→ (v1, o)}) ◦ MS 0 ◦ VS , ; , (var2 = temp; ) ◦ ({}) ◦ FS )
(H, ({temp 7→ (v1, o)}) ◦ MS 0 ◦ VS , var2 = temp; , ({}) ◦ FS )
(H, ({temp 7→ (v1, o)}) ◦ MS 0 ◦ VS , temp, (var2 = •; ) ◦ ({}) ◦ FS )
(H, ({temp 7→ (v1, o)}) ◦ MS 0 ◦ VS , v1, (var2 = •; ) ◦ ({}) ◦ FS )
(H, ({temp 7→ (v1, o)}) ◦ MS 0 ◦ VS , var2 = v1; , ({}) ◦ FS )
Let MS 00 be a variable scope which is the same as MS 0 except that var2 is mapped to v1.
Also let FS = F ◦ FS 0 .
→
→
→
(H, ({temp 7→ (v1, o)}) ◦ MS 00 ◦ VS , ; , ({}) ◦ FS )
(H, ({temp 7→ (v1, o)}) ◦ MS 00 ◦ VS , {}, FS )
(H, MS 00 ◦ VS , F, FS 0 )
At this point the execution of the if statement has been completed and its temporary variable,
temp has been removed from the scope. The variable stack has had the values of var1 and
var2 correctly swapped.
2.4
Well-Typed Configuration
To prove type soundness for MJ, we need to extend our typing rules to configurations. We
write ∆ ` (H, VS , CF , FS ) : τ to mean (H, VS , CF , FS ) is well-typed with respect to ∆ and
will result in a value of type τ (or a valid error state). We break this into three properties:
∆ ` H ok, ∆, H ` VS ok and ∆, H, VS ` CF ◦ FS : void → τ .
The first, ∆ ` H ok, ensures that every field points to a valid object or null, and all the
types mentioned in the heap are in ∆.
[ObjectTyped]
H(o) = (C, F)
C≺τ
∆, H ` o : τ
∆ ` C ok
20
[NullTyped]
∆ ` C ok
∆, H ` null : C
[ObjectOK]
∆, H ` F(fi ) : ∆f (C)(fi )
∆, H ` o ok
[HeapOk]
∀i.1 ≤ i ≤ n
where H(o) = (C, F),
dom(F) = dom(∆f (C)) = f1 . . . fn
∆, H ` o1 ok
...
H ` o2 ok
∆ ` H ok
where dom(H) = {o1 , . . . , on }
The second, ∆, H ` VS ok, constrains every variable to be either a valid object identifier,
or null.
[VarBS]
∆, H ` v1 : C1
...
∆, H ` vn : Cn
∆, H ` BS ok
[VarStackEmpty]
[VarMSEmpty]
∆, H ` [] ok
[VarStack]
where BS = {x1 7→ (v1 , C1 ), . . . , (xn 7→ (vn , Cn )}
∆, H ` VS ok
∆, H ` [] ◦ VS ok
∆, H ` BS ok
∆, H ` MS ◦ VS ok
∆, H ` (BS ◦ MS ) ◦ VS ok
The final property, ∆, H, VS ` FS : τ → τ 0 , types each frame in the context formed
from the heap and variable stack. This requires us to define a collapsing of the context to
form a typing environment. We must extend the typing environment to map, in addition to
variables, object identifiers, o, and holes, •, to values. The collapsing function is defined as
follows.
context({}, [ ])
def
context({}, ({} ◦ MS ) ◦ VS )
def
0
=
=
def
{}
context({}, MS ◦ VS )
context({}, (BS [x 7→ v, C] ◦ MS ) ◦ VS ) = context({}, (BS ◦ MS ) ◦ VS ) ] {x 7→ C}
where x ∈
/ dom(BS) and x ∈
/ dom(context({}, (BS ◦ MS ) ◦ VS )
context(H[o 7→ C, F], VS )
where o ∈
/ dom(H)
def
=
context(H, VS ) ] {o 7→ C}
The syntax of expressions in framestacks is extended to contain both object identifiers
and holes. Hence we require two additional expression typing rules.
[TE-OID]
o:C∈Γ
∆ ` Γ ok
∆; Γ ` o : C
` ∆ ok
[TE-Hole]
•:C∈Γ
∆ ` Γ ok
∆; Γ ` • : C
` ∆ ok
We can know define frame stack typing as follows. We have the obvious typing for an
empty stack. We require special typing rules for the frames that alter variable scoping. We
also require a rule for unrolling sequences because the sequence can contain items to alter
scoping. We then require two rules for typing the rest of the frames; one for frames that
require an argument and one for frames that do not.
21
[TF-StackEmpty]
[TF-StackBlock]
[TF-StackMethod]
∆, H, MS ◦ VS ` FS : void → τ
∆, H, (BS ◦ MS ) ◦ VS ` ({}) ◦ FS : τ 0 → τ
∆, H, VS ` FS : τ → τ 0
∆, H, (BS ◦ []) ◦ VS ` (return •; ) ◦ FS : τ → τ 0
∆; context(H, MS ◦ VS ) ` e : τ
H, VS ` FS : τ → τ 0
∆, H, MS ◦ VS ` (return e; ) ◦ FS : τ 00 → τ 0
[TF-StackMethod2]
[TF-StackIntro]
∆, H, (BS ◦ []) ◦ [] ` [] : τ → τ
∆, H, (BS [x 7→ (null, C)] ◦ MS ) ◦ VS ` FS : void → τ
H, (BS ◦ MS ) ◦ VS ` (C x; ) ◦ FS : τ 0 → τ
where x ∈
/ dom(BS ◦ MS )
[TF-Sequence]
[TF-StackOpen]
∆, H, VS ` (s1 ) ◦ (s2 . . . sn ) ◦ FS : τ → τ 0
∆, H, VS ` (s1 s2 . . . sn ) ◦ FS : τ → τ 0
∆; context(H, VS ), • : C ` OF : τ
H, VS ` FS : τ → τ 0
∆, H, VS ` OF ◦ FS : C → τ 0
where OF 6= (return •; )
[TF-StackClosed]
∆; context(H, VS ) ` CF : τ
H, VS ` FS : τ → τ 0
∆, H, VS ` CF ◦ FS : τ 00 → τ 0
where CF 6= (return e; ), CF 6= ({}), CF 6= s1 . . . sn ∧ n > 1 and CF 6= C x
2.5
Type Soundness
Our main technical contribution in this section is a proof of type soundness of the MJ type
system. In order to prove correctness we first prove two useful propositions in the style
of Wright and Felleisen[23]. The first proposition states that any well typed non-terminal
configuration can make a reduction step.
Proposition 2.2 (Progress) If (H, VS , F, FS ) is not terminal and ∆ ` (H, VS , F, FS ) : τ
then
∃H 0 , VS 0 , F 0 , FS 0 .(H, VS , F, FS ) → (H 0 , VS 0 , F 0 , FS 0 ) .
Proof. By case analysis of F . Details are given in Appendix A.1.
Next we find it useful first to prove the following lemma, which states that subtyping on
frame stacks is covariant.
Lemma 2.3 (Covariant subtyping of frame stack) ∀H, VS , τ1 , τ2 , τ3 . if ∆, H, VS ` FS :
τ1 → τ2 and τ3 ≺ τ1 then ∃τ4 .H, VS ` FS : τ3 → τ4 and τ4 ≺ τ2 .
22
Proof. By induction on the length of FS . Note we only have to consider open frames as all
closed frames ignore their argument. Appendix A.4 contains full details.
We can now prove the second important proposition, which states that if a configuration
can make a transition, then the resulting configuration is of the appropriate type. (This is
sometimes referred to as a subject reduction theorem.)
Proposition 2.4 (Type Preservation) If (H, VS , F, FS ) : τ and (H, VS , F, FS ) → (H 0 , VS 0 , F 0 , FS 0 )
then ∃τ 0 .(H 0 , VS 0 , F 0 , FS 0 ) : τ 0 where τ 0 ≺ τ .
Proof. By case analysis on the reduction step. Lemma 3.2 is needed for the reduction rules
that generate subtypes. Appendix A.5 contains full details of this proof.
We can now combine the two propositions to prove the type soundness of the MJ type
system.
Theorem 2.5 (Type Soundness) If (H, VS , F, FS ) : τ and (H, VS , F, FS ) →∗ (H 0 , VS 0 , F 0 , FS 0 )
where (H 0 , VS 0 , F 0 , FS 0 ) is terminal then either (H 0 , VS 0 , F 0 , FS 0 ) : τ 0 where τ 0 ≺ τ or the configuration is of the form NPE or CCE.
3
MJe: A core Java calculus with effects
The usefulness of MJ as an imperative core calculus for Java hinges on whether it can be used
as a basis for investigations into various operational properties of Java. In this section, we
give the details of one such investigation: the formal development and analysis of an effects
systems for Java, following closely the suggestions of Boyland and Greenhouse [10]. Thus we
will describe a simple extension of MJ with an effects system, calling the resulting language
MJe.
In the rest of this section we begin by giving an overview of the key features of the
effects system of Boyland and Greenhouse. We then define formally MJe, giving the required
extension to the MJ type system, and instrumenting the operational semantics. We conclude
by proving the correctness of our effects system. This question was left open by Greenhouse
and Boyland.
3.1
Greenhouse-Boyland effects system
The effects of Java computation includes the reading and writing of mutable state. As Greenhouse and Boyland observe, given some simple assumptions, knowing the read-write behaviour
of code enables a number of useful optimisations of code. Most effects systems have been defined for functional languages with simple state. The key problem in defining an effects system
for an object-oriented language is to preserve the abstraction facilities that make this style of
programming attractive.
The first problem is deciding how to describe effects. Declaring the effects of a method
should not reveal hidden implementation details. In particular, private field names should
not be mentioned. To solve this, Greenhouse and Boyland introduce the notion of a region
in an object. Thus the regions of an object can provide a covering of the notional state of an
object. The read and write effects of a method are then given in terms of the regions that are
visible to the caller. (Greenhouse and Boyland also introduce two extra notions that we do
23
not address in this paper for simplicity: (1) Hierarchies of regions; and (2) unique references
of objects.)
Greenhouse and Boyland introduce new syntax for Java to (1) define new regions; (2) to
specify which region a field is in; and (3) to specify the read/write behaviour of methods.
Rather than introduce new syntax we insist that these declarations are inserted in the appropriate place in the MJe code as comments. (This is similar to the use of comments for
annotations in Extended Static Checking [4].) For example, here are MJe class declarations
for Point1D and Point2D objects.
class Point1D extends Object{
int x /*in Position*/;
Point1D(int x)
/* reads Position writes Position */
{
this.x = x;
}
void scale(int n)
/* reads Position writes Position */
{ x = this.x * n;
}
}
class Point2D extends Point1D{
int y /*in Position*/;
Point2D(int x, int y) {
/* reads nothing writes Position */
{ super(x); this.y = y;
}
void scale(int n)
/* reads Position writes Position*/
{ this.x = this.x * n;
this.y = this.y * n;
}}
Consider the class Point1D. This defines a field x which is in region Position, and a
method scale that clearly has both a read and a write effect to that region. Class Point2D is
a subclass of Point1D. It inherits field x but also defines a new field y, that is also defined to
be in region Position. It overrides the method scale, but with the same effects annotation,
so it is correct. (Note that this would not have been the case if we simply expressed effects at
the level of fields. Then the scale method in the Point2D class would have more effects—it
writes both fields x and y—than the method it is overriding, and so would be invalid. This
demonstrates the usefulness of the regions concept.)
3.2
MJe definitions
In this section we give the extensions to the definitions of MJ to yield MJe.
Syntax. As we have mentioned above, we have chosen not to extend the Java syntax, but
rather insist that the effects annotations are contained in comments. This ensures the rather
nice property that valid MJe programs are still valid, executable Java programs. Thus the
syntax of MJe is exactly the same as for MJ, with the following exceptions, where r ranges
over region names.
Field definition
fd ::= C f /* in r */;
Method definition
md ::= τ m /* eff */(C1 x1 , . . . , Cn xn ) {s1 . . . sk };
Constructor definition
cnd ::= C /* eff */(C1 x1 , . . . , Cj xj ) {super(e1 , . . . , ek ); s1 . . . sn };
Effect annotation
eff ::= reads reglist writes reglist
reglist ::= r1 , . . . , rn |nothing
24
Effects.
An effect is either empty, ∅, (written nothing in MJe source code), the union of
two effects, written e.g. E1 ∪ E2 , a read effect, R(r), or a write effect, W (r).
Effect
E ::=
∅|W (r)|R(r)|E ∪ E
Equality of effects is modulo the assumption that ∪ is commutative, associative and idempotent, and has ∅ as the unit. A subeffecting relation, ≤, is naturally induced on effects:
E1 ≤ E2 ⇔ ∃E3 .E2 = E1 ∪ E3
Clearly this relation is reflexive and transitive by definition.
There is a curious subtlety with effects and method overriding. Clearly when overriding
a method its effect information must be the same or a subeffect of the overridden method’s
effect. However, consider the following example (where we have dropped the constructor
methods for brevity).
class Cell extends Object{
Object content /* in value */;
void set(Object update)
/*reads nothing writes value*/{
this.contents = update;
};
}
class Recell extends Cell{
Object undo /* in value */;
void set(Object update)
/* reads value writes value */{
this.undo = this.contents;
this.contents = update;
}; }
As it stands, Recell is not a valid subclass of Cell as its set method has more effects
than in Cell. Greenhouse and Boyland [10] (and subsequently [3]) solve this by adding
R(r) ≤ W (r) for all regions r to the subeffecting relation. To keep the subeffecting relation
simple, especially when considering correctness and effect inference, we define the effects
system such that writing to a field has both a read and write effect.
Effects system. We now formally define the effects system. As with MJ, we must initially
describe functions for extracting typing information from the program before we give the
typing rules.
The class table, ∆, must now take account of the effect and region information contained
in the MJe annotations. ∆m and ∆c are extended to return the effects of the methods
and constructors, and ∆f is extended to provide the region for each field. We also define a
function, effect, that translates an MJe effect annotation into its associated effect. Because of
the difficulties mentioned above, this function translates a write effect annotation, writes r
to the effect R(r) ∪ W (r), for some region r.
Method Type
def
∆m (C)(m) =
(
C → τ !effect(eff )
∆m (C 0 )(m)
where md i = τ m/∗eff ∗/(C x){. . .}
where m ∈
/ md 1 . . . md n
25
where class C extends C 0 {fd cnd md 1 . . . md n } ∈ p
Constructor Type
def
∆c (C) = C!effect(eff )
where class C extends C 0 {fd cnd md } ∈ p
and cnd = C/∗eff ∗/(C x){s}
Field Type
(
(C 00 , ri )
∆f (C)(f ) =
∆f (C 0 )(f )
def
where fd i = C 00 f /* in ri */; , for some i, 1 ≤ i ≤ k and ∆f (C 0 )(f ) ↑
otherwise
where class C extends C 0 {fd 1 . . . fd k cnd md } ∈ p
Effect annotation translation
effect(reads r1 , . . . , rn writes rn+1 , . . . , rn+m )
def
= R(r1 ) ∪ . . . ∪ R(rn ) ∪ W (rn+1 ) ∪ R(rn+1 ) ∪ . . . ∪ W (rn+m ) ∪ R(rn+m )
When a method is overridden, to preserve subtyping the new method must not modify
any additional regions. Hence we must add a subeffecting constraint to the judgement for
forming well-formed methods, as follows.
[T-MethOk1]
∆ ` µ ok
∆ ` C.m ok
where ∆m (C)(m) = µ!E, C ≺1 C 0 , ∆m (C 0 )(m) = µ0 !E 0 , µ = µ0
and E ≺ E 0
[T-MethOk2]
∆ ` µ ok
∆ ` C.m ok
where ∆m (C)(m) = µ!E, C ≺1 C 0 and m 6∈ dom(∆m (C 0 ))
The typing rules must also be extended to carry the effect information. Typing judgements
are now of the form ∆; Γ ` e : τ !E, where ∆, Γ and τ are as before, and E is the effect. Only
four rules actually introduce effects and are given below. The [TS-FieldWrite] rule handles
method subtyping by introducing both read and write effects. [TE-Method] and [TE-New]
are both extended to lookup the effects of the code from the annotation.
26
[TE-FieldAccess]
[TS-FieldWrite]
∆; Γ ` e : C2 !E
∆; Γ ` e.f : C1 !E ∪ R(r)
∆; Γ ` e1 : C1 !E1
∆; Γ ` e2 : C2 !E2
where ∆f (C1 )(f ) = (C3 , r)
∆; Γ ` e1 .f = e2 ; : void!E1 ∪ E2 ∪ W (r) ∪ R(r) and C2 ≺ C3
∆, Γ ` e : C 0 !E
∆, Γ ` e1 : C1 !E1 . . . ∆, Γ ` en : Cn !En
[TE-Method]
∆, Γ ` e.m(e1 , . . . , en ) : C!E ∪ E 0 ∪ E1 ∪ . . . ∪ En
[TE-New]
where ∆f (C2 )(f ) = (C1 , r)
∆, Γ ` e1 : C10 !E1 . . . ∆, Γ ` en : Cn0 !En
∆, Γ ` new C(e1 , . . . , en ) : C!E ∪ E1 ∪ . . . ∪ En
where
∆m (C 0 )(m) = C10 , . . . , Cn0 → C!E 0
and C1 ≺ C10 . . . Cn ≺ Cn0
where ∆c (C) = C1 , . . . , Cn !E
and C10 ≺ C1 . . . Cn0 ≺ Cn
The other typing rules require trivial modifications. The axioms introduce the empty
effect and the other rules simply combine the effects from their premises.
The final extension to the typing rules is to check that the effects of method and constructor bodies are valid with respect to the annotations.
where Γ = {this : C, x : C}
and mbody(C, m) = (x, s)
and ∆m (C)(m) = (C → τ !E)
and C 0 ≺ C 00 and E 0 ≤ E
∆, Γ ` s : τ !E 0
[T-MDefn]
∆ ` mbody(C, m) ok
∆, Γ ` e1 : C10 !E1
...
∆, Γ ` en : Cn0 !En
[T-CSuper]
∆, Γ ` super(e1 , . . . , en ); : void!E ∪ E1 ∪ . . . ∪ En
[T-CObject]
where Γ(this) = C and C ≺1 C 0
and ∆c (C 0 ) = C1 , . . . , Cn !E
and C10 ≺ C1 , . . . , Cn0 ≺ Cn
C ≺1 Object
Γ, this : C ` super(); : void!∅
∆, Γ ` super(e); : void!E1
∆, Γ ` s : void!E2
[T-CDefn]
∆ ` C cok
where Γ = this : C, x : C
and ∆c (C) = C!E and
cnbody(C) = (x, super(e); s)
and E1 ∪ E2 ≤ E
Now let us consider the dynamics of MJe. We shall instrument the MJ operational semantics to trace effectful computations. (This is used to demonstrate consistency between
E
the semantics and effects). A single reduction step is now written (H, VS , CF , FS ) −→
(H 0 , VS 0 , CF 0 , FS 0 ), where E is a trace of the effects of the step. The only significant instrumentation is in the [E-FieldAccess] and [E-FieldWrite] rules, which are annotated with R(r)
and W (r) respectively. The rest of the rules have the ∅ effect annotation. We define the
E
transitive and reflexive closure of this relation, −→∗ , as the obvious union of the annotations.
27
R(r)
where o ∈ dom(H) and H(o) = (C, F)
and F(f ) = v and ∆f (C)(f ) = (C 0 , r)
[E-FieldAccess]
(H, VS , o.f, FS ) −→ (H, VS , v, FS )
[E-FieldWrite]
(H, VS , o.f = v; , FS ) −→ (H 0 , VS , ; , FS ) where H(o) = (C, F), f ∈ dom(F),
F0 = F[f 7→ v], ∆f (C)(f ) = (C 0 , r)
H 0 = H[o 7→ (C, F0 )]
3.3
W (r)
Correctness
Our main technical contribution in this section is a proof of correctness of the effects system
of MJe. As we have mentioned earlier, this was not addressed by Greenhouse and Boyland.
Our choice of instrumenting the MJ operational semantics means that the correctness proof
is essentially an adaptation of the type soundness proof for the MJ type system. Thus in
order to prove correctness we first prove two useful propositions.
The first proposition states that any well typed non-terminal configuration can both make
a reduction step, and that any resulting effect is contained in the effects inferred by the effects
system.
Proposition 3.1 (Progress) If (H, VS , F, FS ) is not terminal and (H, VS , F, FS ) : τ !E
then
E0
∃H 0 , VS 0 , F 0 , FS 0 .(H, VS , F, FS ) −→ (H 0 , VS 0 , F 0 , FS 0 ) and E 0 ≤ E .
Proof. By case analysis of the frame F . Further details are given in Appendix A.6.
Next we find it useful first to prove the following lemma, which states that subtyping on
frame stacks is covariant.
Lemma 3.2 (Covariant subtyping of frame stack with effects) ∀H, VS , τ1 , τ2 , τ3 , E. if
H, VS ` FS : τ1 → τ2 !E1 and τ3 ≺ τ1 then ∃τ4 , E2 .H, VS ` FS : τ3 → τ4 !E2 , τ4 ≺ τ2 and
E2 ≤ E 1 .
Proof. By induction on the length of the frame stack FS . Note we only have to consider
open frames as all closed frames ignore their argument. Further details are given in Appendix
A.7.
We can now prove the second important proposition, which states that if a configuration
can make a transition, then the resulting configuration is of the appropriate type and effect.
Proposition 3.3 (Type Preservation) If (H, VS , F, FS ) : τ !E1 and (H, VS , F, FS ) →
(H 0 , VS 0 , F 0 , FS 0 ) then ∃τ 0 , E2 .(H 0 , VS 0 , F 0 , FS 0 ) : τ 0 !E2 where τ 0 ≺ τ and E2 ≤ E1 .
Proof. By case analysis on the reduction step. Lemma 3.2 is needed for the reduction rules
that generate subtypes. Full details are given in Appendix A.8.
We can now combine the two propositions to prove the correctness of the MJe effects
system.
28
E0
Theorem 3.4 (Correctness) If (H, VS , F, FS ) : τ !E and (H, VS , F, FS ) −→∗ (H 0 , VS 0 , F, FS 0 )
where (H 0 , VS 0 , F, FS 0 ) is terminal then either (H 0 , VS 0 , F 0 , FS 0 ) : τ 0 !E 00 where τ 0 ≺ τ , E 0 ≤ E
and E 00 ≤ E; or the configuration is of the form NPE or CCE.
3.4
Effect Inference
In the previous section we defined an effects system, where fields are declared to be in abstract
regions and methods annotated with their read/write behaviour with respect to these regions,
and proved its correctness. The obvious question, not formally addressed by Greenhouse and
Boyland, is whether the method effects can be inferred automatically, assuming that fields
have been ‘seeded’ with their regions. In this section we show that this is possible, and give
an outline of an algorithm which is then proved correct.
Our approach automatically generates the most general annotations for each method and
constructor. We first extend the grammar for effects with variables, where X ranges over
these effects variables.
We then further extend the types system so that it generates a series of constraints. A
constraint is written E ≤ X which is intended to mean that effect X is at least the effects E.
A constraint set is then a set of such constraints.
Effect
E ::=
Constraint Sets
R ::= {E1 ≤ X1 , . . . , En ≤ Xn }
∅|W (r)|R(r)|X|E ∪ E
A substitution, θ, is a function that maps effects variables to effects. It can be extended
pointwise over effects. We say that a substitution θ satisfies a constraint E ≤ X if θ(E) ≤
θ(X). The key to our inference algorithm is the generation of two constraint sets. The
first arises from the effects in the bodies of the methods and constructors; the second from
subtyping.
For the inference algorithm we do not have annotations on methods to describe their
effects. Instead we generate a fresh effect variable to represent their effects. We write θ(∆)
to denote the substitution θ on the effects variables contained in ∆; we suppress the rather
routine details.
∆m (C)(m) =
(
C → C 00 !X
∆m (C 0 )(m)
where md i = C 00 m(C~x){. . .}
where m ∈
/ md 1 . . . md n
where class C extends C 0 {fd cnd md 1 . . . md n } ∈ p
and X is a fresh effect variable.
We must extend the definitions of both a well-formed class table, ` ∆ ok, and well-formed
programs, ∆ ` p ok to generate constraints. We will first give the new definition of a wellformed class table. Instead of checking the subtyping requirements on each methods’ effects,
we generate a constraint for each check. The rules then union together the constraints.
29
where
∆m (C)(m) = µ!X
C ≺1 C 0
∆m (C 0 )(m) = µ0 !Y
µ = µ0
where
∆m (C)(m) = µ!X
C ≺1 C 0
m 6∈ dom(∆m (C 0 ))0
∆ ` µ ok
[T-MethOk1]
∆ `i C.m : {X ≤ Y }
[T-MethOk2]
∆ ` µ ok
∆ `i C.m : ∅
where dom(∆m ) = {C1 , . . . , Cn } ,
∆ `i C1 .m1,1 : R1,1
...
∆ `i Cn .mn,nn : Rn,nn dom(∆m (C1 )) = {m1,1 , . . . , m1,n1 },
[T-MethsOk]
...,
∆ `i ∆m : R1,1 ∪ . . . ∪ Rn,nn
dom(∆m (Cn )) = {mn,1 , . . . , mn,nn }
[T-Defni ]
∆ ` i ∆m : R
` ∆c ok
`i ∆ : R
` ∆f ok
Next we will give the extension to the definition of a well-formed program. Here we must
produce constraints that the effect variable associated to a method or constructor has at least
the effects of the body.
∆, Γ ` s : C 0 !E 0
[T-MDefn]
∆ `i mbody(C, m) : {E 0 ≤ X}
∆, Γ ` super(e); : void!E1
∆, Γ ` s : void!E2
[T-CDefn]
∆ `i C cok : {E1 ∪ E2 ≤ X}
where Γ = this : C, x : C and
mbody(C, m) = (x, s)
and ∆m (C)(m) = (C → C 00 !E)
and C 0 ≺ C 00
where Γ = this : C 0 , x : C
and cnbody(C) = (x, super(e); s)
and ∆c (C 0 ) = C!X
We use the following three rules to generate the constraint set for the whole program.
[T-Methods]
∆ `i mbody(C, m1 ) : R1 . . . ∆ `i mbody(C, mn ) : Rn
∆ `i C mok : R1 ∪ . . . ∪ Rn
where
dom(∆m (C)) = {m1 , . . . , mn }
∆ `i C mok : R1
∆ `i C mok : Rn
∆ `i C cok : R10
...
∆ `i C cok : Rn0
[T-ClasDef]
0
∆ `i p : R1 ∪ R1 ∪ . . . ∪ Rn ∪ Rn0
We have given ways of generating constraints based on the class hierarchy, ` i ∆ : R1 , and
also constraints from the implementations of methods and constructors, ∆ ` p : R 2 . We note
immediately that these constraints need not have a unique solution. Consider the following
excerpt from a class definition.
int counter; /* in r */
void count(int x) {
30
this.counter=x; this.count(x-1);
}
Assume that the method count is assigned the effect variable X as its effects annotation.
From the typing judgement for the method body we produce the constraint:
W (r) ∪ R(r) ∪ X ≤ X
Clearly there are infinitely many solutions to this constraint. However the minimum solution
is what is needed (in this case it is {X 7→ W (r) ∪ R(r)}).
Lemma 3.5 (Existence of minimum solution) Given a constraint set {E1 ≤ X1 , . . . , En ≤
Xn } there is a unique, minimal solution.
Proof. A proof was given by Talpin and Jouvelot [19]. They also give an algorithm for
finding the minimum solution of a set of constraints.
Our main result in this section is that our inference algorithm is correct, in that it generates
valid effect annotations. First we prove a couple of useful lemmas about effect substitution.
The first states that if a substitution satisfies the constraints generated by a class table, then
applying it to the class table produces a well-formed class table; which is obvious by definition.
Lemma 3.6 (Substitutions satisfy subtyping) If ` ∆ : Rs and θ satisfies Rs then `
θ(∆) ok.
Proof. Straight from definitions.
The next lemma states that effect substitutions preserve typing judgements.
Lemma 3.7 (Effect substitution preserves typing) If ∆; Γ ` t : C!E, ` ∆ : Rs and θ
satisfies Rs then θ(∆); Γ ` t : C!θ(E)
Proof. By induction on the typing relation. This uses the fact that substitution is ∪continuous, to prove the rules that compose effects from their premises. It requires the
previous lemma to prove the axioms.
From these two lemmas we can prove the correctness of our effect inference algorithm.
Theorem 3.8 (Inference algorithm produces sound annotations) If ∆ ` p : Rd , `
∆ : Rs and θ satisfies Rs ∪ Rd then θ(∆) ` p ok.
Proof. This follows from the definitions and repeated use of Lemma 3.7. See Appendix §A.9
for more details.
31
4
Related work
There have been many works on formalising subsets of Java. Our work is closely related to,
and motivated by, Featherweight Java [12]. We have upheld the same philosophy, by keeping
MJ a valid subset of Java. However FJ lacks many key features we wish to model. It has no
concept of state or object identity, which we feel essential for developing usable specification
logics. Our work has extended FJ to contain what we feel are the important imperative
features of Java.
Another related calculus is Classic Java [7], which embodies many of the language features of MJ. However, Classic Java is not a valid subset of Java, as it uses let-binding to
model both sequencing and locally scoped variables. Hence several features of Java, such as
its block structured state and unusual syntactic distinction on promotable expressions, are
not modelled directly. However their motivation is different to ours: they are interested in
extending Java with mixins, rather than reasoning about features of Java.
Eisenbach, Drossopoulou, et al. have developed type soundness proofs for various subsets
of Java [5, 6]. In fact they consider a larger subset of Java than MJ as they model exceptions
and arrays, however they do not model block structured scoping. Our aims were to provide
an imperative core subset of Java rather than to prove soundness of a large fragment.
There has been some other related work on effects systems. The use of regions by Greenhouse and Boyland is similar to Leino’s use of data groups [15]. Data groups are also an abstract means for encapsulating fields. The key difference is that regions own a field uniquely,
where as a field can belong to many data groups. Leino uses data groups to discuss only
writes/updates of fields. Clarke and Drossopoulou [3] have also defined an effects system for
a Java-like language. Their system uses ownership types rather than regions to delimit the
scope of computational effects. A more detailed comparison is left to future work.
5
Conclusions and future work
In this paper we propose Middleweight Java, or MJ, as a contender for an imperative core
calculus for Java. We claim that it captures most of the complicated imperative features
of Java, but is compact enough to make rigorous proofs feasible. To justify this claim we
considered its extension with an effects system proposed by Greenhouse and Boyland [10].
We formally defined the effects system and an instrumented operational semantics, and proved
the correctness of the effects systems (a question not addressed by Greenhouse and Boyland).
We then considered the question of effects inference, namely the inference of the effects in the
method and constructor bodies. We defined an algorithm and proved its correctness.
There are surprising problems when considering subject reduction for Java, primarily concerning the use of substitution. This was discussed in detail recently on the types forum [11].
Consider the following method declaration:
Object m (boolean b, Object a1, Object a2) {
return (b ? a1 : a2);
}
Imagine unfolding a method call m(v1,v1) using substitution. Typechecking the resulting
statement return (b ? v1 : v2); runs into difficulty because we have lost the ‘static’ types
of v1 and v2 (Object), and it may be the case that their ‘dynamic’ types are not “cast
32
convertible”. In MJ we do not model parameter passing by substitution, and so in fact this
problem does not arise (method invocations create new scopes that contain the ‘static’ typing
information). Of course we still have two “Stupid” type rules: one for casting and one for
comparison.
Clearly further work remains. In terms of the effects system, we are currently investigating
extending MJe with two other properties suggested by Greenhouse and Boyland; namely
hierarchies of regions, and alias types. It remains to be seen if our proofs of correctness can
be easily adapted to this richer setting.
Recent work on formalising the various generic extensions of Java, like Generic Java (GJ) [2],
have been based on FJ [12]. Indeed this was part of the motivation for the design of FJ. Recently Alan Jeffrey has discovered a problem with the type inference used in GJ [13]. His
counterexample exploits the manipulation of state to generate a runtime exception. Thus it
appears that adopting a functional core calculus of Java to study generics is an oversimplification. We intend to use MJ as a basis for studying the proposals for generic extensions to
both Java [2] and C] [14]
In other work, we are developing a logic for reasoning about MJ programs, based on the
bunched logic approach pioneered by O’Hearn, Reynolds and Yang [17, 18].
Acknowledgements
Portions of this work were presented at the Workshop for Object-Oriented Developments [1].
We are grateful to the referees for their comments on this work.
We are grateful for discussions with Andrew Kennedy, Alan Lawrence and Martin Odersky.
This work was supported by EPSRC (Parkinson) and EU AppSem II (Bierman).
References
[1] G.M. Bierman and M.J. Parkinson. Effects and effect inference for a core Java calculus.
In Proceedings of WOOD, volume 82 of ENTCS, 2003.
[2] G. Bracha, M. Odersky, D. Stoutamire, and P. Wadler. Making the future safe for the
past: Adding genericity to the Java programming language. In Proceedings of OOPSLA’98, October 1998.
[3] D. Clarke and S. Drossopolou. Ownership, encapsulation and the disjointness of type
and effect. In Proceedings of OOPSLA, November 2002.
[4] D.L. Detlefs, K.R.M. Leino, G. Nelson, and J.B. Saxe. Extended static checking. Technical Report 159, Compaq Systems Research Center, 1998.
[5] S. Drossopoulou, S. Eisenbach, and S. Khurshid. Is the Java type system sound? Theory
and Practice of Object Systems, 7(1):3–24, 1999.
[6] Sophia Drossopoulou, Tanya Valkevych, and Susan Eisenbach. Java type soundness
revisited. URL http://citeseer.nj.nec.com/article/drossopoulou00java.html.
[7] M. Flatt, S. Krishnamurthi, and M. Felleisen. A programmer’s reduction semantics for
classes and mixins. Technical Report TR-97-293, Rice University, 1997. Corrected June,
1999.
33
[8] D.K. Gifford and J.M. Lucassen. Integrating functional and imperative programming.
In Proceedings of ACM Lisp and Functional Programming, 1986.
[9] J. Gosling, B. Joy, G. Steele, and G. Bracha. The Java Language Specification. Addison
Wesley, second edition, 2000.
[10] A. Greenhouse and J. Boyland. An object-oriented effects system. In ECOOP, volume
1628 of Lecture Notes in Computer Science, pages 205–229, 1999.
[11] H. Hosoya, B. Pierce, and D. Turner. Subject reduction fails in Java. Note sent to the
types mailing list, June 1998.
URL http://www.cis.upenn.edu/~bcpierce/types/archives/1997-98/msg00400.html.
[12] A. Igarashi, B.C. Pierce, and P. Wadler. Featherweight Java: A minimal core calculus for
Java and GJ. ACM Transactions on Programming Languages and Systems, 23(3):396–
450, 2001.
[13] A. Jeffrey. Generic Java type inference is unsound. Note sent to the types mailing list,
December 2001.
URL http://www.cis.upenn.edu/~bcpierce/types/archives/current/msg00849.html.
[14] A.J. Kennedy and D. Syme. The design and implementation of generics for the .NET
common language runtime. In Proceedings of PLDI, june 2001.
[15] K.R.M. Leino. Data groups: Specifying the modification of extended state. In Proceedings
of OOPSLA, 1998.
[16] J.M. Lucassen. Types and effects, towards the integration of functional and imperative
programming. PhD thesis, MIT Laboratory for Computer Science, 1987.
[17] P.W. O’Hearn, J.C. Reynolds, and H. Yang. Local reasoning about programs that alter
data structures. In Proceedings of CSL, 2001.
[18] J.C. Reynolds. Separation logic: A logic for shared mutable data structures. In Proceedings of LICS, 2002.
[19] J.-P. Talpin and P. Jouvelot. Polymorphic type, region, and effect inference. Journal of
Functional Programming, 2(3):245–271, 1992.
[20] J.-P. Talpin and P. Jouvelot. The type and effect discipline. Information and Computation, 111(2):245–296, 1994.
[21] P. Wadler. The essence of functional programming. In Proceedings of Principles of
Programming Languages, 1992.
[22] P. Wadler. The marriage of effects and monads. In International Conference on Functional Programming, 1998.
[23] A. Wright and M. Felleisen. A syntactic approach to type soundness. Information and
Computation, 115(1):38–94, 1994.
34
A
A.1
Proofs
Progress Lemma
Proposition 2.2 (Progress) If (H, VS , CF , FS ) is not terminal and ∆ ` (H, VS , CF , FS ) :
τ then ∃H 0 , VS 0 , CF 0 , FS 0 .(H, VS , CF , FS ) → (H 0 , VS 0 , CF 0 , FS 0 ) .
Proof. By case analysis of CF . By considering all well typed configurations, we can show
how each term can reduce.
(1)
∆ ` H ok
(2)
(3)
∆, H ` VS ok ∆, H, VS ` CF ◦ FS : void → τ
∆ ` (H, VS , CF , FS ) : τ
We will assume the above typing judgement, and for each term we provide a corresponding
reduction rule.
Case: CF = (return e; ) As it is well typed we know V S = M S ◦V S 0 . This has two possible
cases of reducing.
Case: e = v this can reduce by [E-Return].
Case: e 6= v this can reduce by [EC-Return].
Case: CF = ({}) As this is well typed, we know V S = (BS ◦ M S) ◦ V S 0 and hence this can
reduce by [E-Block].
Case: CF = C x; As it is well typed, we know V S = (BS ◦ M S) ◦ V S 0 and also x ∈
/
context(V S). From the definition of context we can see that this gives x ∈
/ dom(BS ◦
M S) and hence it can reduce by [E-VarIntro].
Case: CF = x We know that x must be in context(H, VS ), and as x can not be in H it
must come from VS and more precisely in MS where VS = MS ◦ VS 0 , hence it can
reduce by [E-VarAccess].
Case: CF = o .
Case: F S 6= [] reduces by [E-Sub].
Case: F S = [] This is a terminal framestack: (H, V S, o, []).
Case: CF = null .
Case: F S 6= [] reduces by [E-Sub].
Case: F S = [] This is a terminal framestack: (H, V S, null, []).
Case: CF = e.f This can be broken into three cases.
Case: e = null reduces by [E-NullField].
Case: e = o reduces by [E-FieldAccess].
Case: e 6= v reduces by [EC-FieldAccess].
35
Case: CF = e0 .m(e1 , . . . , en ) If e0 6= v then [EC-Method1] will apply. If any of e1 , . . . , en
are not values then [EC-Method2] will be applied. Otherwise we have the case where
CF = v 0 .m(v1 , . . . , vn ) in which case [E-Method] applies if v 0 = o or [E-NullMethod] if
v 0 = null.
Case: CF = new C(e1 , . . . , en ) reduces by [EC-New] when ei 6= v or [E-New] otherwise.
Case: CF = (C)e .
Case: e 6= v reduces by [EC-Cast].
Case: e = o reduces by either [E-Cast] or [E-InvCast] depending on the type of o.
Case: e = null this can reduce by [E-NullCast].
Case: CF =; .
Case: FS 6= [] reduces by [E-Skip].
Case: FS = [] This is a terminal state.
Case: CF = s1 . . . sn reduces by [EC-Seq].
Case: CF = if (e1 == e2 ){s1 } else {s2 } .
Case: e1 6= v1 reduces by [EC-If1]
Case: e2 6= v2 ∧ e1 = v1 reduces by [EC-If2]
Case: e1 = v1 ∧ e2 = v2 can reduce by either depending on the test [E-If1] and [E-If2].
Case: CF = x = e .
Case: e = v this can reduce using [E-VarWrite] if x is in the environment. The typing
rule tells us that this must be true as context(H, V S) ` x : C
Case: e 6= v this can reduce by [EC-VarWrite].
Case: CF = e.f = e0 .
Case: e 6= v reduces using [EC-FieldWrite1].
Case: e = v ∧ e0 6= v 0 reduces using [EC-FieldWrite2].
Case: e = o ∧ e0 = v 0 reduces using [E-FieldWrite].
Case: e = null ∧ e0 = v 0 reduces using [E-NullWrite].
Case: CF = e; reduces by [EC-ExpState].
Case: CF = super(e1 , . . . , en ) If all the expressions are values then this can reduce by [ESuper], otherwise it can reduce by [EC-Super].
36
A.2
Frame stack sequence typing
We require two lemmas for dealing with typing sequences when they are added to the frame
stack.
Lemma A.1 (Block) ∆, H, MS ◦ VS ` FS : void → τ ∧ ∆; context(H, (BS ◦ MS ) ◦ VS ) `
s : void ⇒ ∆, H, (BS ◦ MS ) ◦ VS ` s ◦ {} ◦ FS : void → τ
Lemma A.2 (Return) ∆, H, VS ` FS : τ → τ 0 ∧ ∆; context(H, MS ◦ VS ) ` s1 . . . sn : τ ⇒
∆, H, MS ◦ VS ` s1 ◦ . . . ◦ sn ◦ FS : void → τ where sn is of the form return e;
The two lemmas have very similar proofs. We will present the proof to the first lemma
here.
Proof. We prove this by induction on the size of s.
Base Case:
∆, H, MS ◦ VS ` FS : void → τ ∧ ∆; context(H, (BS ◦ MS ) ◦ VS ) ` s : void
⇒ ∆, H, (BS ◦ MS ) ◦ VS ` s ◦ {} ◦ FS : void → τ
(4)
This is trivial as the proof of ∆, H, (BS ◦ MS ) ◦ VS ` s ◦ {} ◦ FS : void → τ is given by
the two assumptions.
Inductive Case: Assume:
∀BS .∆, H, MS ◦ VS ` FS : void → τ ∧ ∆; context(H, (BS ◦ MS ) ◦ VS ) ` s2 . . . sn
⇒ H, (BS ◦ MS ) ◦ VS ` s2 . . . sn ◦ {} ◦ FS : void → τ
(5)
∆, H, MS ◦ VS ` FS : void → τ
(6)
∆; context(H, (BS ◦ MS ) ◦ VS ) ` s1 . . . sn
(7)
∆, H, (BS 0 ◦ MS ) ◦ VS ` s1 . . . sn ◦ {} ◦ FS : void → τ
(8)
0
Prove:
We proceed by a case analysis of s1 .
Case: s1 = Cx; From (7) we can deduce ∆; context(H, (BS 0 [x → (C, null)] ◦ MS ) ◦ VS ) `
s2 . . . sn : void. Using this and specialising the the inductive hypothesis (5) allows us
to deduce ∆, H, (BS [x → (C, null)] ◦ MS ) ◦ VS ` s2 . . . sn ◦ {} ◦ FS : void → τ . This
is exactly what we require to prove (8), which completes this case.
Case: s1 6= Cx; From (7) we can deduce
∆; context(H, (BS 0 ◦ MS ) ◦ VS ) ` s2 . . . sn : void
0
∆; context(H, (BS ◦ MS ) ◦ VS ) ` s1 : void
(9)
(10)
Specialising the inductive hypothesis and using (9) gives us ∆, H, (BS ◦ MS ) ◦ VS `
s2 . . . sn ◦ {} ◦ FS : void → τ , which combined with (10) gives (8), and completes this
case.
37
A.3
Heap Extension preserves typing
Lemma A.3 If ∆, H, VS ` FS : τ → τ 0 , o ∈
/ dom(H) and ∆ ` C then ∆, H[o 7→
(C, . . .)], VS ` FS : τ → τ 0
Proof. by induction on the length of FS . All the rules for typing the frame stack follow
by induction, except for [TF-OpenFrame], [TF-ClosedFrame] and [TF-Method2]. To prove
these it suffices to prove
∆; Γ ` F : τ ∧ ∆ ` C ⇒ ∆; Γ, o : C ` F : τ
where o ∈
/ dom(Γ).
We can see ∆ ` Γ ok ∧ ∆ ` C ⇒ ∆ ` Γ, o : C ok from the definition of Γ ok. We can use
this to prove all the axioms of the typing judgement. The rules follow directly by induction
except for [TS-Intro]. For this rule, we must prove that x and o are different. This is true as
they are from different syntactic categories.
A.4
Covariant subtyping of frame stack
Lemma 2.3 (Covariant subtyping of frame stack) ∀H, VS , τ1 , τ2 , τ3 . if ∆, H, VS ` FS :
τ1 → τ2 and τ3 ≺ τ1 then ∃τ4 .∆, H, VS ` FS : τ3 → τ4 and τ4 ≺ τ2 .
Proof. By induction on the size of FS .
Base Case: FS = []
This can only be typed by [TF-StackEmpty]. This rule is covariant, as the only constraint is
that the argument and the result types are the same.
Inductive Step
Show that covariant subtyping holds for F ◦ FS by assuming it holds for FS . If F is closed
then this is trivial, because all closed frames ignore their argument. Hence their typing and
effects can not depend on their argument’s type. Next we must consider the open frames.
First let us consider return •; as this affects the typing environment. This is covariantly
typed if the remainder of the frame stack is covariantly typed. Hence this is true by the
inductive hypothesis. For the remainder of the cases it suffices to prove:
∆; Γ, • : τ ` OF : τ1 ∧ τ2 ≺ τ ⇒ ∃τ3 . ∆; Γ, • : τ2 ` OF : τ3 ∧ τ3 ≺ τ1
|
|
{z
} | {z }
{z
} | {z }
(12)
(11)
(13)
(14)
We proceed by case analysis on OF .
Case: OF = if (• == e){s1 } else {s2 }; From (11) we know
∆; Γ, • : τ ` • : τ
(15)
(16)
(17)
∆; Γ, • : τ ` e : C ∆; Γ, • : τ ` s1 : void ∆; Γ, • : τ ` s2 : void
∆; Γ, • : τ ` if (• == e){s1 } else {s2 }; : void
We need to prove
38
∆; Γ, • : τ1 ` • : τ1
(18)
(19)
(20)
∆; Γ, • : τ1 ` e : C ∆; Γ, • : τ1 ` s1 : void ∆; Γ, • : τ1 ` s2 : void
∆; Γ, • : τ1 ` if (• == e){s1 } else {s2 }; : void
Clearly as e, s1 and s2 do not contain •. Therefore we have (15) ⇒ (18), (16) ⇒ (19)
and (17) ⇒ (20), which completes this case.
Case: OF = if (v == •){s1 } else {s2 }; Similar to previous case.
Case: OF = •.f From assumptions we know
∆; Γ, • : τ ` • : τ ∆f (τ )(f ) = C2
∆; Γ, • : τ ` •.f : C2
As τ2 ≺ τ and field types can not be overridden, we know ∆f (τ2 )(f ) = C2 , which lets
us prove
∆; Γ, • : τ2 ` •.f : C2
as required.
Case: OF = •.f = e; Similar to previous case.
Case: OF = •.m(e1 , . . . , en ) Similar to previous case, except we use the fact method types
can not be overridden.
Case: OF = x = •; We know from the assumptions that
∆; Γ, • : τ ` x : C ∆; Γ, • : τ ` • : τ
∆; Γ, • : τ ` x = •; : void
τ ≺C
As the sub-typing relation is transitive and τ2 ≺ τ , we can see this is well typed for
• : τ2 , and the result type is the same.
Case: OF = v.f = •; Similar to previous case.
Case: OF = v.m(v1 , . . . , vi−1 , •, ei+1 , . . . , en ) Similar to previous case.
Case: OF = new C(v1 , . . . , vi−1 , •, ei+1 , . . . , en ) Similar to previous case.
Case: OF = super(v1 , . . . , vi−1 , •, ei+1 , . . . , en ) Similar to previous case.
Case: OF = (C)• The result type of this frame does not depend on the argument type. The
three possible typing rules for this case combined to only require • is typeable. Hence
this case is trivial.
39
A.5
Type preservation
Proposition 2.4 If ∆ ` (H, VS , F, FS ) : τ and (H, VS , F, FS ) → (H 0 , VS 0 , F 0 , FS 0 ) then
∃τ 0 .∆ ` (H 0 , VS 0 , F 0 , FS 0 ) : τ 0 where τ 0 ≺ τ .
Proof. This done by case analysis on the → relation. By considering all possible reductions
we can show that the program will always reduce to a valid state, and that the state will be
a subtype of the configuration type before the reductions.
All the rules for controlling the order of evaluation, defined in §3, are proved by trivially
restructuring the typing derivations. We will show the rest of the cases now.
Case: [E-Skip]
∆ ` (H, VS , ; , F ◦ FS ) : τ
Assume
∆ ` (H, VS , F, FS ) : τ
Prove
(21)
(22)
(25)
∆, H, VS ` F ◦ FS : void → τ ∆; context(H, VS ) `; : void
(24)
∆, H ` VS ok
∆, H, VS ` (; ) ◦ F ◦ FS : void → τ
∆ ` (H, VS , ; , F ◦ FS ) : τ
(23)
∆ ` H ok
This lets us deduce the following:
∆ ` H ok
23
24
25
∆, H ` VS ok
∆, H, VS ` F ◦ FS : void → τ
∆ ` (H, VS , F, FS ) : τ
and hence prove 22.
Case: [E-Sub]
∆ ` (H, VS , v, F ◦ FS ) : τ
Assume
∆ ` (H, VS , F [v/•], FS ) : τ
Prove
(26)
(27)
We split the proof into two cases. Firstly where F = CF (a closed frame) and secondly
where F = OF (frame with a hole).
The proof needs to be split into two cases. One where F is a closed term, CF , and the
second where F is a term requiring an expression, OF .
Case: F = CF From (26) we get
(28)
` H ok
(29)
∆, H ` VS ok
(30)
∆; context(H, VS ) ` v : τ 0 ∆, H, VS ` CF ◦ FS : void → τ
∆, H, VS ` (v) ◦ CF ◦ FS : void → τ
∆ ` (H, VS , v, CF ◦ FS ) : τ
We know that CF = CF [v/•] as CF has no holes. Hence we can deduce
∆ ` H ok
28
29
30
∆, H ` VS ok
∆, H, VS ` CF ◦ FS : void → τ
∆ ` (H, VS , CF , FS ) : τ
∆ ` (H, VS , CF [v/•], FS ) : τ
40
Which proves (27).
Case: F = OF From (26) we get
(31)
(32)
0
∆; context(H, VS ), • : τ ` OF : τ
00
∆, H, VS ` FS : τ 00 → τ
∆, H, VS ` OF ◦ FS : τ 0 → τ
(36)
(35)
(33)
∆ ` H ok
∆; context(H, VS ) ` v : τ 0 (36)
(34)
∆, H ` VS ok ∆, H, VS ` (v) ◦ OF ◦ FS : void → τ
∆ ` (H, VS , v, OF ◦ FS ) : τ
From this we can deduce
(37)
33
∆ ` H ok
∆; context(H, VS ) ` OF [v/•] : τ 00 ∆, H, VS ` FS : τ 00 → τ
34
∆, H ` VS ok
∆, H, VS ` OF [v/•] ◦ FS : void → τ
∆ ` (H, VS , OF [v/•], FS ) : τ
We need to show that (35)∧(31) ⇒ (37). We can break this into two cases v = null
and v = o. The first is trivially true as null can have any type that is required,
and hence plugging null in for any identifier will still leave the term typeable. The
second case looks harder but this actually just alpha conversion, as both • and o
are identifiers.
Hence we have proved (27).
Case: [E-Return]
Assume
Prove
∆ ` (H, MS ◦ VS , return v; , FS ) : τ
(38)
∆ ` (H, VS , v, FS ) : τ
(39)
From (38) we have the following proof tree
(42)
(40)
∆ ` H ok
(43)
0
∆; context(H, MS ◦ VS ) ` v : τ
∆, H, VS ` FS : τ 0 → τ
(41)
∆, H ` MS ◦ VS ok
∆, H, MS ◦ VS ` (return v; ) ◦ FS : void → τ
∆ ` (H, MS ◦ VS , return v; , FS ) : τ
For (39) we need the following tree.
(45)
∆ ` H ok
40
∆; context(H, VS ) ` v : τ 0 ∆, H, VS ` FS : τ 0 → τ
(44)
∆, H ` VS ok
∆, H, VS ` (v) ◦ FS : void → τ
∆ ` (H, VS , v, FS ) : τ
From the definition of ∆, H ` VS ok we can see that (41) ⇒ (44).
43
We know a value’s typing is not affected by the variable scope. Hence (42) ⇒ (45),
which proves (39).
41
32
Case: [E-VarAccess]
∆ ` (H, MS ◦ VS , x, FS ) : τ
(46)
eval (MS , x) = (v, C2 )
(47)
∆ ` (H, MS ◦ VS , v, FS ) : τ1
(48)
Assume
Prove
τ1 ≺ τ
(49)
From (46) we can deduce the following tree.
(50)
∆ ` H ok
(52)
(53)
∆; context(H, MS ◦ VS ) ` x : C2 ∆, H, MS ◦ VS ` FS : C2 → τ
(51)
∆, H ` MS ◦ VS ok
∆, H, MS ◦ VS ` (x) ◦ FS : void → τ
∆ ` (H, MS ◦ VS , x, FS ) : τ
To prove (48)
(54)
(55)
∆; context(H, MS ◦ VS ) ` v : C3 ∆, H, MS ◦ VS ` FS : C3 → τ1
∆, H, MS ◦ VS ` (v) ◦ FS : void → τ1
(56)
∆ ` H ok
50
51
∆, H ` MS ◦ VS ok
∆ ` (H, MS ◦ VS , v, FS ) : τ1
(56)
Combining (47) and (51), we can deduce that v is well typed and that C3 ≺ C2 , which
proves (54)
We use the Covariant Subtyping of the stack lemma
C3 ≺ C2 ∧ (53) ⇒ (55) ∧ (49)
This completes the case.
Case: [E-VarWrite]
Assume
∆ ` (H, MS ◦ VS , x = v; , FS ) : τ
update(MS , (x 7→ v)) ↓
0
∆ ` (H, MS , ; , FS ) : τ
Prove
where MS 0 = update(MS , x 7→ v).
From (57) we can deduce the following tree.
(62)
(61)
∆; context(H, MS ◦ VS ) ` x : C ∆; context(H, MS ◦ VS ) ` v : C 0
∆; context(H, MS ◦ VS ) ` x = v; : void
(60)
42
(63)
C0 ≺ C
(57)
(58)
(59)
(64)
∆ ` H ok
(66)
(60) ∆, H, MS ◦ VS ` FS : void → τ
(65)
∆, H ` MS ◦ VS ok ∆, H, MS ◦ VS ` (x = v; ) ◦ FS : void → τ
∆ ` (H, MS ◦ VS , x = v; , FS ) : τ
To deduce (59) we need the following proof.
0
∆; context(H, MS ◦ VS ) `; : void
TS-Skip
(67)
0
∆, H, MS ◦ VS ` FS : void → τ
∆, H, MS 0 ◦ VS `; ◦FS : void → τ
(69)
` H ok
(68)
64
∆, H ` MS 0 ◦ VS ok
(69)
∆ ` (H, MS 0 ◦ VS , ; , FS ) : τ
We know the only difference between MS and MS 0 is that one of the variable blocks
contains the new value x 7→ (v, C). For this to be well-typed we require that
∆, H ` v : C
This is given by (62) and (63), which with (65), gives (68).
The typing information contained in MS and MS 0 is identical. Therefore (66) ⇒ (67).
Case: [E-VarIntro]
∆ ` (H, (BS ◦ MS ) ◦ VS , C x; , FS ) : τ
Assume
0
∆ ` (H, (BS ◦ MS ) ◦ VS , ; , FS ) : τ
Prove
0
(70)
(71)
where BS 0 = BS [x 7→ (null, C)]
From (70) we can deduce the following tree.
(72)
x∈
/ dom(BS ◦ MS )
(74)
∆ ` H ok
(73)
0
∆, H, (BS ◦ MS ) ◦ VS ` FS : void → τ
(76)
(75)
(76)
∆, H ` (BS ◦ MS ) ◦ VS ok ∆, H, (BS ◦ MS ) ◦ VS ` (C x; ) ◦ FS : void → τ
∆ ` (H, (BS ◦ MS ) ◦ VS , x, FS ) : τ
To prove (71)
∆; context(H, (BS 0 ◦ MS ) ◦ VS ) `; : void
TS-Skip
∆, H, (BS 0 ◦ MS ) ◦ VS ` FS : void → τ
∆, H, (BS 0 ◦ MS ) ◦ VS ` (; ) ◦ FS : void → τ
(77)
43
73
∆ ` H ok
74
(78)
(77)
0
∆, H ` (BS 0 ◦ MS ) ◦ VS ok
∆ ` (H, (BS ◦ MS ) ◦ VS , ; , FS ) : τ
By the definition of ∆, H ` VS ok we know that (75) ⇒ (78), as the additional variable
is valid. Hence proving (71)
Case: [E-If1]
∆ ` (H, VS , if (v1 == v2 ){s1 } else {s2 }, FS ) : τ
Assume
∆ ` (H, VS , {s1 }, FS ) : τ
Prove
(79)
(80)
From (79) we can deduce
∆; Γ ` v1 : τ1
(82)
∆ ` H ok
∆; Γ ` v2 : τ2
(81)
∆; Γ ` {s1 } : void
84
∆; Γ ` {s2 } : void
(84)
(85)
∆; Γ ` if (v1 == v2 ){s1 } else {s2 }) : void ∆, H, VS ` FS : void → τ
(83)
∆, H ` VS ok
∆, H, VS ` (if (v1 == v2 ){s1 } else {s2 }) ◦ FS : void → τ
∆ ` (H, VS , if (v1 == v2 ){s1 } else {s2 }, FS ) : τ
where Γ = context(H, VS ).
From this we can deduce the following tree, and hence prove (80).
∆ ` H ok
82
81
85
∆; Γ ` {s1 } : void
∆, H, VS ` FS : void → τ
83
∆, H ` VS ok
∆, H, VS ` {s1 } ◦ FS : void → τ
∆ ` (H, VS , {s1 }, FS ) : τ
Case: [E-If2] Identical to previous.
Case: [E-BlockIntro]
∆ ` (H, MS ◦ VS , {s}, FS ) : τ
Assume
Prove
∆ ` (H, ({} ◦ MS ) ◦ VS , s, {} ◦ FS ) : τ !E
(86)
(87)
We know
(88)
∆, ` H ok
(90)
∆; context(H, MS ◦ VS ) ` s : void
(91)
∆; context(H, MS ◦ VS ) ` {s} : void ∆, H, MS ◦ VS ` FS : void → τ
(89)
∆, H ` MS ◦ VS ok
∆, H, MS ◦ VS ` {s} ◦ FS : void → τ
∆ ` (H, MS ◦ VS , {s}, FS ) : τ
We need to prove
44
∆ ` H ok
88
(92)
∆, H ` ({} ◦ MS ) ◦ VS ok
∆, H, ({} ◦ MS ) ◦ VS ` (s) ◦ {} ◦ FS : void → τ
∆ ` (H, ({} ◦ MS ) ◦ VS , s, {} ◦ FS ) : τ
89
Using (90) and the definition of context, we can deduce ∆; context(Heap, ({} ◦ MS ) ◦
VS ) ` s : void. Using this, (91) and Lemma A.1 gives us (92), hence completing this
case.
Case: [E-Cast]
Assume
∆ ` (H, VS , (C1 )o, FS ) : τ
(93)
H(o) = (C2 , F)
(94)
C2 ≺ C 1
(95)
0
(96)
0
τ ≺τ
(97)
∆ ` (H, VS , o, FS ) : τ
Prove
From (93) we can deduce
(98)
∆ ` H ok
(100)
∆; context(H, VS ) ` (C1 )o : C1 ∆, H, VS ` FS : C1 → τ
(99)
∆, H ` VS ok
∆, H, VS ` ((C1 )o) ◦ FS : void → τ
∆ ` (H, VS , (C1 )o, FS ) : τ
We need to prove
∆ ` H ok
98
∆, H ` VS ok
99
∆; context(H, VS ) ` o : C2
94
(101)
∆, H, VS ` FS : C2 → τ 0
∆, H, VS ` (o) ◦ FS : void → τ 0
∆ ` (H, VS , o, FS ) : τ 0 )
We use the Covariant subtyping lemma this to gives us:
(95) ∧ (100) ⇒ (97) ∧ (101)
Hence proving (96) and (97).
Case: [E-FieldWrite]
Assume
Prove
∆ ` (H, VS , o.f = v; , FS ) : τ
0
∆ ` (H , VS , ; , FS ) : τ
where H(o) = (C, F) and H 0 = H[o 7→ (C, F[f 7→ v])]
From 102 we can deduce
(105)
∆; Γ ` o : C
(107)
(106)
∆; Γ ` v : C2 ∆f (C)(f ) = (C3 )
(104)
45
(108)
C2 ≺ C 3
0
(102)
(103)
(104)
(111)
(110)
∆; Γ ` o.f = v; : void ∆, H, VS ` FS : void → τ
∆, H ` VS ok
∆, H, VS ` (o.f = v; ) ◦ FS : void → τ
∆ ` (H, VS , o.f = v; , FS ) : τ
(109)
∆ ` H ok
where Γ = context(H, VS ).
We need to prove the following tree.
(112)
∆ ` H 0 ok
∆, H 0 ` VS ok
110
0
∆; context(H , VS ) `; : void
TS-Skip
(113)
0
∆, H , VS ` FS : void → τ
∆, H 0 , VS ` (; ) ◦ FS : void → τ
∆ ` (H 0 , VS , ; , FS ) : τ
Both H and H 0 contain the same typing information, wrt the function context, hence
(111) ⇒ (113).
As we know (109) we only need to prove H 0 ` o ok for (112). To prove this we are
required to show ∆, H 0 ` v : ∆f (C)(f ), which is given by (105), (106), (107) and (108).
Case: [E-FieldAccess]
Assume
Prove
(H, VS , o.f, FS ) : τ
(114)
o ∈ dom(H)
(115)
H(o) = (C, F)
(116)
F(f ) = v
(117)
(H, VS , v, FS ) : τ1
(118)
τ1 ≺ τ
(119)
From (114) we can deduce
(120)
∆ ` H ok
(121)
∆, H ` VS ok
(122)
(123)
∆; context(H, VS ) ` o : C ∆f (C)(f ) = C2
(124)
∆; context(H, VS ) ` o.f : C2
∆, H, VS ` FS : C2 → τ
∆, H, VS ` (o.f ) ◦ FS : void → τ
∆ ` (H, VS , o.f, FS ) : τ
We need to prove
∆ ` H ok
120
(125)
(126)
∆; context(H, VS ) ` v : C3 ∆, H, VS ` FS : C3 → τ1
121
∆, H ` VS ok
∆, H, VS ` (v) ◦ FS : void → τ1
∆ ` (H, VS , v, FS ) : τ1
By the definition of ∆ ` H ok, we can see that (120) ∧ (122) ∧ (123) ⇒ (125) ∧ C 3 ≺ C2
and using covariant subtyping of the stack gives us C3 ≺ C2 ∧ (124) ⇒ (126) ∧ (119).
Hence proving (118) and (119).
46
Case: [E-New]
∆ ` (H, VS , new C(v1 , . . . , vn ), FS ) : τ
Assume
0
∆ ` (H , MS ◦ VS , super(e); s, (return o; ) ◦ FS ) : τ
Prove
(127)
(128)
where
o∈
/ dom(H)
(129)
F = {f 7→ null|(f ∈ dom(∆f (C))}
∆C (C) = [C1 , . . . , Cn ]
cnbody(C) = [x1 , . . . , xn ], super(e); s
(130)
(131)
(132)
MS = {this 7→ (o, C), x1 7→ (v1 , C1 ), . . . , xn 7→ (vn , Cn )} ◦ []
H 0 = H[o 7→ (C, F)]
(133)
(134)
From (127) we can deduce
Γ ` v1 : C10
(138)
` H ok
(136)
...
Γ ` vn : Cn0
C10 ≺ C1
(135)
(137)
...
Cn0 ≺ Cn
(135)
(140)
Γ ` new C(v1 , . . . , vn ) H, VS ` FS : C → τ
(139)
H ` VS ok H, VS ` (new C(v1 , . . . , vn )) ◦ FS : void → τ
(H, VS , new C(v1 , . . . , vn ), FS ) : τ
where Γ = context(H, VS ).
To prove (128)
(141)
0
` H ok
(142)
0
H ` MS ◦ VS ok
(143)
0
H , MS ◦ VS ` (super(e); s) ◦ (return o; ) ◦ FS : void → τ
(H 0 , MS ◦ VS , super(e); s, (return o; ) ◦ FS ) : τ
We can prove (141) as the only change from H to H 0 is adding a new object. As all its
fields are set to null we know this is object is valid, and all the other objects are valid
by (138).
We need to prove that MS is a valid variable scope wrt to H 0 . We know that this
is of type C and so is o so this part is okay, as the subtype relation is reflexive. The
other variables are all bound to values that are known to be valid from (136) and (137).
Hence using (139) we know (142).
Using heap extension preserves typing we know (140) ⇒ ∆, H 0 , VS ` FS : C → τ .
From [T-ConsOK] we know that ∆; Γ ` super(e); s : void where Γ = {this : C, x 1 :
C1 , . . . , xn : Cn }. We can see from the definition of context that context(H 0 , MS ◦VS ) =
Γ]Γ0 where Γ0 contains only type assignments for object ids. By the extension property
we know ∆; context(H 0 , MS ◦ VS ) ` super(e); s : void. We can extend this to give
∆; context(H 0 , MS ◦ VS ) ` super(e); s return o; : C, which allows us to use lemma A.2
to complete the case.
47
Case: [E-Method]
Assume
Prove
(H, VS , o.m(v1 , . . . , vn ), FS ) : τ
(144)
(H, MS ◦ VS , s return e; , FS ) : τ
(145)
where
H(o) = (C, F)
(146)
∆m (C)(m) = C1 , . . . , Cn → C
0
(147)
mbody(C, m) = [x1 , . . . , xn ], s return e;
(148)
MS = {this 7→ (o, C), x1 7→ (v1 , C1 ), . . . , xn 7→ (vn , Cn )} ◦ []
(149)
From (144) we can deduce
(151)
Γ`o:C
Γ ` v1 : C10
(152)
...
Γ ` vn : Cn0
C10 ≺ C1
Γ ` o.m(v1 , . . . , vn ) : C 0
(150)
(153)
...
Cn0 ≺ Cn
(156)
(154)
` H ok
(150) H, VS ` FS : C 0 → τ
(155)
H ` VS ok H, VS ` (o.m(v1 , . . . , vn )) ◦ FS : void → τ
H, VS , o.m(v1 , . . . , vn ), FS ) : τ
where Γ = context(H, VS ).
To prove (145) we need the following tree
` H ok
154
(158)
(157)
H ` MS ◦ VS ok H, MS ◦ VS ` (s return e; ) ◦ FS : void → τ
H, MS ◦ VS , (s return e; ), FS ) : τ
We need to show that MS is a valid variable scope. This can be seen to be true in the
same way as the previous case, except for the typing of object which comes from (151).
So we know (155) ⇒ (157)
TODO
We know from methods ok that ∆; Γ ` s return e; : C where Γ = {o : C, x1 : C1 , . . . , xn :
Cn }, by similar reasoning to previous case and again using lemma A.2 we can complete
the case. TODO: Requires Covariant subtyping lemma as well.
Case: [E-MethodVoid] Same as previous case, with a trivial alteration for the return .
Case: [E-Super]
Assume
Prove
(H, MS ◦ VS , super(v1 , . . . , vn ), FS ) : τ
0
(H, MS ◦ MS ◦ VS , s, return o; , FS ) : τ
48
0
(159)
(160)
(161)
where
MS (this) = (o, C)
(162)
0
∆c (C ) = {C1 , . . . , Cn }
(163)
0
cnbody(C ) = [x1 , . . . , xn ], s
0
(164)
0
MS = {this 7→ (o, C ), x1 7→ (v1 , C1 ), . . . , xn 7→ (vn , Cn )} ◦ []
C ≺1 C
0
(165)
(166)
From (159) we can deduce
Γ ` v1 : C10
(170)
` H ok
(169)
...
Γ ` vn : Cn0
C10 ≺ C1
Γ ` super(v1 , . . . , vn ) : void
(167)
(168)
...
Cn0 ≺ Cn
C ≺ 1C
(172)
(167)
H,
VS
`
FS : void → τ
(171)
H ` MS ◦ VS ok H, MS ◦ VS ` (super(v1 , . . . , vn )) ◦ FS : void → τ
H, MS ◦ VS , super(v1 , . . . , vn ), FS ) : τ
where Γ = context(H, VS ).
To prove (160) we need the following proof
` H ok
170
(173)
(174)
0
H ` MS ◦ MS ◦ VS ok
0
H, MS ◦ MS ◦ VS ` (s) ◦ (return o; ) ◦ FS : void → τ
H, MS 0 ◦ MS ◦ VS , s, (return o; ) ◦ FS ) : τ
We need to show that MS 0 is a valid variable scope. This is true in the same way as
the previous case, except for the typing of this, which comes from MS , because super
requires Γ to contain this. We know this to be valid from (171) and (167), so we know
(171) ⇒ (173)
We can prove (174) in the same way as for [E-New]. Hence we have proved the final
case.
A.6
Progress with effects.
Proposition 3.1 If (H, VS , F, FS ) is not terminal and (H, VS , F, FS ) : τ !E then
E0
∃H 0 , VS 0 , F 0 , FS 0 .(H, VS , F, FS ) −→ (H 0 , VS 0 , F 0 , FS 0 ) and E 0 ≤ E .
Proof. The proof of this lemma is identical to the proof in §A.1 except for the following two
cases.
Case: F = e.f Typing this will introduce the effect R(r) where the field f is in the region
r. This can be broken into three cases.
49
Case: e = null reduces by [E-NullField].
Case: e = o reduces by [E-FieldAccess]. This reduction has effect R(r), which we have
in the typing judgement.
Case: e 6= v reduces by [EC-FieldAccess].
Case: F = e.f = e0 . The typing of this introduces the effect W (r) where f is in the region
r.
Case: e 6= v reduces using [EC-FieldWrite1].
Case: e = v ∧ e0 6= v 0 reduces using [EC-FieldWrite2].
Case: e = o ∧ e0 = v 0 reduces using [E-FieldWrite]. This reduction rule has the effect
of W (r), which is in the typing derivation.
Case: e = null ∧ e0 = v 0 reduces using [E-NullWrite].
A.7
Covariant subtyping of frame stack with effects
Lemma 3.2 ∀H, VS , τ1 , τ2 , τ3 , E. if H, VS ` FS : τ1 → τ2 !E1 and τ3 ≺ τ1 then ∃τ4 , E2 .H, VS `
FS : τ3 → τ4 !E2 , τ4 ≺ τ2 and E2 ≤ E1 .
Proof. This is proved in the same way as the lemma without effects. The following cases
need slight extension.
Case: OF = •.f From assumptions we know
∆; Γ, • : τ ` • : τ !∅ ∆f (τ )(f ) = (C2 , r)
∆; Γ, • : τ ` •.f : C2 !R(r)
As τ1 ≺ τ and field types and regions can not be overridden, we know ∆f (τ1 )(f ) =
(C2 , r), which lets us prove
∆; Γ, • : τ1 ` •.f : C2 !R(r)
as required.
Case: OF = •.f = e; Similar to previous case.
Case: OF = •.m(e1 , . . . , en ) Similar to previous case, except we use the fact method types
and effects can not be overridden.
50
A.8
Type and effect preservation lemma
Proposition 3.3 If ∆ ` (H, VS , F, FS ) : τ !E1 and (H, VS , F, FS ) → (H 0 , VS 0 , F 0 , FS 0 ) then
∃τ 0 .∆ ` (H 0 , VS 0 , F 0 , FS 0 ) : τ 0 !E2 where τ 0 ≺ τ and E2 ≤ E1 .
Proof. The proof proceeds in exactly the same way as for §A.5. The majority of cases either
do not require any additional proof or follow directly from the extended covariant lemma. We
will now present the additional information required to prove the extended type preservation
proof.
The following cases follow using the same proof, but using the rules of the effect system:
[E-Sub], [E-Skip], [E-Return], [E-VarWrite], [E-VarIntro]
The following cases follow from extending the proof trees and using the extended covariant
lemma: [E-VarAccess], [E-Cast]
The remaining cases all alter the effect type. We must show that the new effect type is a
subeffect of the old.
Case: [E-If1]
Assume
Prove
∆ ` (H, VS , if (v1 == v2 ){s1 } else {s2 }, FS ) : τ !E
(175)
0
(176)
E ≤E
(177)
∆ ` (H, VS , {s1 }, FS ) : τ !E
0
If we consider {s1 } to have the effects E1 , {s2 } to have the effects E2 and FS to have
the effects E3 . Then we can see, before the reduction the effects are E1 ∪ E2 ∪ E3 and
after they are E1 ∪ E3 , which is clearly a subeffect.
Case: [E-If2] Identical to previous.
Case: [E-FieldWrite] Before the reduction this has the effects E ∪W (r) and after E. Again
the reduction only reduces the effects.
Case: [E-FieldAccess] Same as previous case but with a read effect.
Case: [E-New] Before the reduction the effects are the effect annotation given in ∆, after
the reduction they are the effects of the actual implementation, but from [T-ConsOK]
we know the implementation must has less effects. Hence the reduction reduces the
effects.
Case: [E-Super] Same as previous case.
Case: [E-Method] Same as previous case, except we need to use the same property of
methods.
Case: [E-MethodVoid] Same as previous case.
51
A.9
Inference algorithm produce sound annotations.
Theorem 3.8 If ∆ ` p : R1 , ` ∆ : R2 and θ satisfies R1 ∪ R2 then θ(∆) ` p : ∅.
Proof. Assume
∆ `i p : R
(178)
0
(179)
0
(180)
`i ∆ : R
θ satisfies R ∪ R
Prove
θ(∆) ` p
(181)
From (178) we know
∀j
∆ `i Cj ok : Rj
Rj ⊆ R
(182)
(183)
Which gives
∆ `i Cj mok : Rj0
∆ `i Cj cok :
Rj0
Rj =
∪
Rj00
Rj00
(184)
(185)
(186)
From (184) we know
∀k
0
∆ `i Cj .mk : Rj,k
(187)
∆, Γ ` body : C 0 !E 0
(189)
0
Rj,k
⊆
Rj0
(188)
From (187) we know
0
Rj,k
0
=E ≤X
∆m (Cj )(mk ) = µ!X
(190)
(191)
By Lemma 3.7 with (189), (180) and (179) we can deduce
θ(∆), Γ ` body : C 0 !θ(E 0 )
(192)
0 ⊆ R (by (183),(186) and (188)), (180) and (190) we have
As Rj,k
θ(E 0 ) ≤ θ(X)
(193)
By definition of substitution on ∆ and (191)
θ(∆m )(Cj )(mk ) = µ!θ(X)
52
(194)
Using (192), (193) and (194) we can deduce
θ(∆) ` Cj .mk ok
(195)
As we have shown this for an arbitrary k without any assumptions about it, so we know
θ(∆) ` Cj mok
(196)
θ(∆) ` Cj cok
(197)
θ(∆) ` Cj ok
(198)
A similar proof allows us to prove
which allows us to prove
for arbitrary j, so we can assume it for all j. Hence
θ(∆) ` p
53
Was this manual useful for you? yes no
Thank you for your participation!

* Your assessment is very important for improving the work of artificial intelligence, which forms the content of this project

Download PDF

advertisement