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

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

Download PDF

advertisement