Java Exceptions Throw no Surprises 1 Introduction

0 downloads 0 Views 277KB Size Report
In Java 5] exceptions are objects, and behave as such (i.e. they can be assigned, passed as ..... First they check whether ... If these two requirements are satis ed, then the assignment is performed. Notice that all ... According to the sixth rule, an ...
Java Exceptions Throw no Surprises Sophia Drossopoulou, Tanya Valkevych Department of Computing, Imperial College of Science, Technology and Medicine email: fsd,[email protected] Abstract

We outline a proof of type soundness for Java exceptions and exception handling. We distinguish between normal execution, where no exception is thrown { or, more precisely, any exception thrown is handled { and abnormal execution, where an exception is thrown and not handled. The type system distinguishes normal types which describe the possible outcomes of normal execution, and abnormal types which describe the possible outcomes of abnormal execution. The type of a term consists of its normal type and its abnormal type. With this set-up we prove subject reduction. The meaning of our subject reduction theorem is stronger than usual: it guarantees that normal execution returns a value of a type compatible with the normal type of the term, and that abnormal execution throws an exception compatible with the abnormal type of the term.

1 Introduction Exceptions and exception handling aim to support the development of robust programs with reliable error detection, and fast error handling [4]. Exceptions have been incorporated in most larger programming languages, e.g. in Ada, C++, SML, etc. When an exception is raised it propagates control to its dynamic predecessor until the nearest enclosing handler for that exception is reached. Java and some other languages require methods to declare all the exceptions they may raise and fail to handle themselves. In this paper we present a summary of our formalization of the dynamic and static semantics of Java related to exceptions. The complete formalization of the Java subset we have considered so far can be found in [3]. In earlier works [1, 2] we de ned Javas, a provably safe subset of Java containing some primitive types, interfaces, classes with instance variables and instance methods, inheritance, hiding of instance variables, overloading and overriding of instance methods, arrays, the null value, object creation, assignment, eld and array access, method call and dynamic method binding, exceptions and exception handling. b r r Java  Javas ?! C Java  Java ; Java

#

Type =

#

#

P

#

Type = Type w Type

The execution of Java programs requires some type information at run-time, e.g. eld and method descriptors. For this reason, we de ned Javab, an enriched version of Javas containing compile-time type information necessary for the execution of method call and eld access. Compilation C maps Javas terms to Javab terms preserving their types. Execution of run-time terms may produce terms which are not 1

described by Javab. Therefore we extended Javab, obtaining Javar, which describes run-time terms, including addresses. Execution may proceed normally , where no exception is thrown { or, more precisely, any exception thrown is handled { or abnormally , where an exception is thrown and not handled. A term therefore has a type which is a pair of a normal type and an abnormal type. The normal type describes the possible outcomes of normal execution. A normal type may be a class, or an interface, or a primitive type, like int, char, nil, or void. The abnormal type describes the possible outcomes of abnormal execution. An abnormal type is represented by a set of subclasses of the prede ned Throwable class. Normal types are \usual" types, whereas abnormal types correspond to e ect systems [9, 16]. Interestingly, rewriting a term either preserves both the normal type and the abnormal type, or it produces an exception, which is described by the abnormal type only, i.e. by the e ect system. We de ne widening for types. A type T widens to another type T0 , if the normal part of T is identical to or a subclass or subinterface of, the normal part of T0 , and if the abnormal part of T consists of classes which are unchecked exceptions, or identical to or subclasses of, corresponding ones in the abnormal part of T0 . We prove a subject reduction theorem [19] which states that term execution preserves types up to widening. Our de nition of types as pairs of normal ?abnormal types makes the meaning of the subject reduction theorem stronger, namely that normal execution will return a value of a type compatible with the normal type, and abnormal execution will throw an exception whose class is a subclass of one of the classes in its abnormal type. The rest of this paper is organized as follows. In section 2 we describe Java exceptions through an example. Because exceptions are run-time events, we concentrate on the run-time language Javar de ned in section 3. The operational semantics for Javar related to exceptions is described in section 4. In section 5 we introduce two kinds of Java terms { exception terms and exception-free terms. In section 6 we discuss the types of Javar terms. In section 7 we de ne widening state the subject reduction theorem. Finally, in section 8 we draw some conclusions and describe further work. In appendix A1 we outline the requirements for complete, well-formed Javab programs. In appendix A2 we demonstrate the execution of an example.

2 An example demonstrating exceptions Exception handling in programming languages supports the development of robust programs [4]. When an exception is raised { thrown in Java terminology { control is transferred to the nearest handler { catch clause in Java { for that exception found in the dynamic call stack. In Java [5] exceptions are objects, and behave as such (i.e. they can be assigned, passed as parameters, etc.) until they are thrown. A method must mention in its header all the checked exceptions that might escape from execution of its body, and the compiler ensures that callers of a method either handle this method's potential exceptions, or they themselves explicitly mention these exceptions in their headers. Exceptions are objects of the prede ned class Throwable or its subclasses. The unchecked exceptions are exempt from the requirement of being listed in the headers. Unchecked exceptions are the subclasses of class RuntimeException describing run-time errors, e.g. dereferencing null or division by zero, and the subclasses of Error describing linkage and virtual machine errors, e.g. the absence of a *.class le or veri cation errors. All the other exceptions are checked exceptions. C++ [14] and SML [10] exceptions are similar to Java exceptions in the way 2

they are propagated and caught, and in that exceptions behave as ordinary objects/values unless explicitly thrown. Unlike Java, C++ exceptions do not need to be objects of a special exception class; SML exceptions are special values of any type which have been annotated as exception. Neither C++, nor SML programmers are expected to declare the exceptions which escape a function.1 The following example Pwr demonstrates some of these issues. Pwr =

class Worry extends Exception fg class Illness extends Exception f int severity; Illness treat() throws Worry f if (...) throw new Worry(); else return this;g Illness cure() f severity = -10; return this; g g

class Person f int age; Illness diagnose()f return new Illness(); g void act() throws Worry, Illness f try f if (...) throw diagnose().treat(); else catch( Illness i)f i.cure(); g g void live() throws Worry, Illness f act(); Doctor d=null; d.act(); g void study() f try f act(); g catch(Throwable x)fage=age+2;g g

=age+1; g

age

g

class Doctor extends Person f int act() throws Illness f throw diagnose(); g void live() f int i=diagnose().cure().severity; g

g

Consider the following declarations and initializations:

; ;

Person peter peter Doctor david david

= new Person(); = new Doctor();

The above example demonstrates:  Exception classes are those declared as subclasses of Throwable. The prede ned class Exception is a subclass of Throwable, therefore the classes Worry and Illness are exception classes.  Exceptions are thrown by throw statements, e.g. execution of david.act() throws an exception of class Illness.  Unless thrown, objects of an exception class behave as normal objects. So, execution of david.live() will not throw any exception: the call diagnose() returns an Illness object, which executes the method cure, and then returns 1 This is why the SML formal description[10] does not have a concept corresponding to our abnormal types.

3

 

  





its eld severity. Therefore, the method live in class Doctor has no throws clause. A method header may mention one or more exception classes in its throws clause. The throws clause of a method header must mention the class or superclass of any checked exception that might be thrown during evaluation of its body, and during evaluation of any overriding method. Therefore, the method header of live in class Person, which calls the method act, has to mention the exceptions Illness and Worry, because these may be thrown when executing the nested call of act, i.e. the classes Illness and Worry. Similarly, the method header for act in class Person has to mention the class Worry, because it might be thrown in its method body, and it also has to mention the exception Illness, because Illness is a possible exception of the overriding method act from class Doctor. Unchecked exceptions need not be mentioned in throws clauses. That is why, although execution of Doctor d=null; d.act(); inevitably throws a NullPointerException, this exception need not be mentioned in the throws clause of the header of the method live. An exception thrown and caught within a method need not be mentioned in the header of that method. For example, the method study of class Person does not mention any exception because any exception potentially thrown by the nested method call act() is caught by the catch clause. Once an exception is thrown, it is propagated through the dynamic chain of method calls, until a handler is reached. Thus, an exception of class Worry will be thrown when evaluating peter.act(). On the other hand, the call peter.study() will not terminate with a thrown exception, because any exception is caught inside the method body of study. Exception propagation follows the dynamic chain of method calls, and does not take overridden methods into account. Thus, david.act() will throw an exception of class Illness. This exception will not be caught in the catch clause of act in class Person; it will be propagated to the expression that contained the method call david.act(). In general, it is unpredictable whether a term will evaluate with or without exceptions. For example, depending on the outcome of the evaluation of the condition in the method treat, the term new Illness().treat() may throw an exception of class Worry, or it may return an object of class Illness.

3 Java , the run-time language r

Because exceptions are run-time events we concentrate our discussion on the runtime language Javar. Javar is used only for execution, and therefore it contains terms only, and does not contain class, method, or interface de nitions. Javar terms correspond to Java terms, except that we sometimes omit separators like f, g, (, ), , and ; when obvious. Furthermore, Javar is enriched with type information for execution (which is part of Javab) and also allows for run-time artifacts not possible in the source language or Javab. The type information necessary for execution consists of eld and method descriptors. For example, the Javas eld access peter.age is represented in Javab and Javar as peter.[Person]age. Also the Javas method call peteract() is represented 4

in Javab and Javar as peter.[]act() { the method descriptor is empty because there are no parameters. ::= ( Stmt ;) [ return [Expr] ;] ::= if Expr then Stmts else Stmts j Var = Expr j Expr.MethName ([Expr ]) j throw Expr j try Stmts (catch ClassName Id Stmts) nally Stmts j try Stmts (catch ClassName Id Stmts)+ Expr ::= Value j Var j Expr.[ArgType]MethName (Expr ) j new ClassName() j new SimpleType ([Expr])+ ([]) j Stmts Var ::= Name j Expr.[ClassName]VarName j Expr[Expr ] j this j RefValue.[ClassName]VarName j RefValue[Expr] Value ::= PrimValue j RefValue RefValue ::=  j null PrimValue ::= intValue j charValue j boolValue j ... SimpleType ::= PrimType j ClassName j InterfaceName PrimType ::= bool j char j int j ... Stmts Stmt

Figure 1: Javar terms The run-time artifacts are addresses, the null value in eld access and method call, and statements as expressions. Addresses have the form , 0 , 1 , etc.; they represent references to objects and arrays, and may appear wherever a value is expected, as well as in array access and eld access. Therefore, Javar variables may have the form .[ClassName]VarName or [Expr ], and expressions may have the form . An access to null may arise during evaluation of array access or eld access; therefore, Javar expressions may have the form null.[ClassName]VarName or null[Expr ]. Furthermore, in order to describe method evaluation through inline expansion rather than closures and stacks, we allow an expression in Javar to consist of a sequence of statements, so that in the operational semantics a method call can be rewritten to a statement sequence. In order to give a succinct description of the operational semantics of exception propagation and typing, we introduce the notion of context. The context of an exception, de ned in gure 2, encompasses all enclosing terms up to the nearest enclosing try-catch or try-catch- nally clause, i.e. up to the rst possible position at which the exception might be handled.

4 The operational semantics Evaluation of Javar terms takes place in the context of a state and a program. Thus, we de ne small step rewriting as follows: ; : Javab program ?! Javar-termstate ?! ((Javar-termstate) [ (state)) States map identi ers onto primitive values or addresses, and addresses onto objects or arrays. Objects are annotated by their classes. This annotation is used for example, when binding methods according to the run-time class of the receiver. The objects contain the name of their elds, the classes which contain the eld declaration and the value of these elds. The explanation can be found in e.g. [3]. For example, 10 is a possible state with: 10 (peter) = 11 10 (11 ) = age Person : 0 Person 5

::= ExprCont j VarCont j StmtCont ::= ExprCont.[ClassName]VarName j ExprCont[Expr ] j Expr[ExprCont ] j @A ExprCont ::= new VarType [Expr ]1 ... [ExprCont ]k ... [Expr ]n j ExprCont.[ArgType]MethName(Expr1 ,...Exprn) j Expr.[ArgType]MethName(Expr1 ,...ExprContk ,...Exprn) where n  1; 1  k  n

Context VarCont

j

@A

j

@A

StmtCont ::= VarCont = Expr j Var = ExprCont j if ExprCont then Stmts else Stmts j StmtCont ; Stmt j return ExprCont j throw ExprCont

Figure 2: Javar exception contexts v is not l-ground v v0 0 P v=e v0 =e 0 P

v is l-ground e e0 0 P v=e v=e0 P

val is ground id is an identi er id=val P [id val] [C]f=val P [ f C val]

; are ground  () = [ val0 :::valn?1]T[] :::[] 0 > k; or k > n ? 1 new IndOutBndE(); ;0 ; 0 [k]=val; ;throw 0 ; 0

; ; ;  ; ;

.

; ; ;  ; ;

;

; 0

val k

1

;  ; 7! ;  ; ; ; 7!

m

P

P

; are ground () = [ val0 :::valn?1]T[] :::[] 0kn?1 val does not t T[]1 :::[]m in P;  new ArrStoreE(); ;0 ; 0 [k]=val;  ;throw 0 ; 0 val k

1

; are ground  () = [ val0 :::valn?1]T[] :::[] 0kn?1 val ts T[]1 :::[]m in P;  [k]=val; ; [; k7!val] val k

m

1

P

P

m

P

Figure 3: Assignment execution We discuss some of the rules of the operational semantics. Figure 3 describes the evaluation of assignments. According to the rst rule, the left-hand side is evaluated rst, until it becomes l-ground (i.e. of the form t = id, or t = .[C]f; or t = null.[C]f; or t = [k]; or t = null[k] for some identi er id, class C, eld f, integer k, or address ). Then, according to the next rule, the right-hand side of the assignment is evaluated, up to the point of obtaining a ground term. Assignment to variables or to object components modi es the state accordingly. The last three rules describe assignment to array components. First they check whether the index is within the array bounds; if not, IndOutBndE is thrown. Secondly, they check whether the value ts the array type, and if not, ArrStoreE is thrown. If these two requirements are satis ed, then the assignment is performed. Notice that all rules which throw exceptions, separate the creation of the exception object (i.e. new E(); ;; 0 ) from the actual throwing (i.e. e; ;throw ; 0 ). In other words, there are no rules of the form e; ;throw new E(); . The reason will be explained in section 7. Figure 4 describes the operational semantics related to exceptions. According to P

P

P

6

stmts P stmts0 0 stmts E1 v1 stmts1 stmts0 E1 v1 stmts1 P stmts E1 v1 stmts1 stmts0 E1 v1 stmts1 P

; ; ; try catch ::: catch E v stmts ;  ;try catch ::: catch E v stmts ; 0 try catch ::: catch E v stmts nally stmts + ;  ;try catch ::: catch E v stmts nally stmts + ; 0 stmts;  ; 0 try stmts catch E v stmts ::: catch E v stmts ; ;0 try stmts catch E v stmts ::: catch E v stmts nally stmts + ;  ;stmts + ; 0 e;  ;e0 ;  0 new NullPE(); ;; 0 0 0 throw e;  ;throw e ;  throw null;  ;throw ; 0 cont@A a context cont@throw A;  ;throw ;  () = ::: 8k 2f1:::ng P 6` E v E try throw  catch E v stmts ::: catch E v stmts ; ;throw ; 0 try throw  catch E v stmts ::: catch E v stmts nally stmts + ;  ;stmts + ;throw ;  () =  :::  9i 2f1:::ng : P ` E v E AND 8k 2f1:::i ? 1g P 6` E v E stmts0 = stmts [z=v ]; z new in stmts and in  0 =  [z7!] try throw  catch E v stmts ::: catch E v stmts ; ;stmts0; 0 try throw  catch E v stmts ::: catch E v stmts nally stmts + ;  ;try stmts0 nally stmts + ; 0 n

n

n

n

n

n

n

n

n

n

n 1

n

n

n 1

P

1

1

1

n

n

n

1

1

1

n

n

n

P

n 1

n 1

P

P

P

P

P

P

E

k

P

1

1

1

n

n

n

1

1

1

n

n

n

P

n 1

n 1

E

i

i

P

k

i

i

1

1

1

n

n

n

1

1

1

n

n

n

P

n 1

Figure 4: Exception throwing, propagation and handling

7

n 1

the rst rules, execution of try-catch and try-catch- nally statements proceeds by execution of the try part. If no exception is thrown in the try part, then the most enclosing try-catch statement terminates, whereas the most enclosing trycatch- nally statement proceeds to execute the nally part. A throw statement evaluates the corresponding expression { note that evaluation of that expression might itself throw a further expression. If the expression evaluates to null, then a NullPE is thrown. Once the expression following throw is ground, i.e. once it reaches the form , the exception is propagated. Thus, the exception reaches either the outer level of the program, or it reaches an enclosing try-catch or try-catch- nally statement. According to the sixth rule, an exception of a class not covered2 by any of the classes in the catch clauses is propagated out of an enclosing try-catch statement. Also, an exception of a class not covered by any of the classes in the catch clauses causes execution of the nally part of an enclosing try-catch- nally statement followed by renewed throwing of the original exception. According to the seventh rule, an exception is handled by the rst handler whose class is a superclass of the exception's class: the statements of the corresponding catch clause are executed with the catch clause variable (vi) pointing to the exception object. The nally clause, if any, will follow execution of the handler independently of whether the handler executed normally or abnormally.3 The last two rules in gure 4 re ect the contents of pages 291-294 of the Java speci cation where the evaluation of try-catch and try-catch- nally is described using twenty two rules written in natural language. A contribution of this work is, we believe, the more concise presentation suggested here. Having started with fourteen rewrite rules, we managed to condense them further to this concise description. We hope that our more concise description in uences the next version of the Java speci cation.

5 Exception terms and exception-free terms We distinguish between terms which contain a thrown exception not enclosed in handlers (exception terms), and those that contain at most one thrown exception enclosed in handlers (exception-free terms):

De nition 1 A Java term t is an exception term if t = cont @ throw  A for some context cont @A. A Java term t is an exception-free term if it contains at most one subterm throw  for an address , and it is not an exception term. Thus, the terms throw , and (throw ).[]act() are exception terms. The terms try throw  catch E v stmts , and david.[]act(), and new Illness.[]cure(), and throw new Illness() 4 are exception-free terms. r

r

1

1

1

Note: Javas and Javab terms are exception-free. Note: Exception terms rewrite to exception terms. Note: Exception-free terms rewrite to exception-free terms or to exception terms. Exception terms and exception-free terms are contradictory but not complementary. For example, the term if x then throw 1 ::: else throw 2 ::: is neither an exception term, nor is it exception-free. Our operational semantics does not have rewrite rules for such a term. Luckily, although such terms are syntactically possible, they cannot arise through evaluation of Javab terms. This follows from the three notes from above. 2 3 4

The judgement P ` C v C means that C is a subclass of C . The appendix outlines possible executions of the term peter.[]act() Remember that the term david.[]act() is the compiled form of david.act(). 0

0

8

Note: Any intermediate Javar term reached during evaluation of a compiled Javas term is either an exception-free term or an exception term. Note: Any intermediate Javar term reached during evaluation of a compiled Javas term contains at most one thrown exception. Of course, the above applies only to sequential programs. A similar observation can be formulated for each thread of a concurrent Java program.

::: ; ;  `r v : T k ET ; ;  `r e : T0 k ET0 ` w T ; ;  `r v=e : void k ET [ ET0

P V P V P T0 P V

 () = ::: P; V;  `r  : C k ; C

; ;  `r ei : T0i k ETi i 2f0:::ng; n  0 ` w Ti i 2f1:::ng FirstFit (P; m; T00 ; T1  :::  Tn ) = fmethg P; V;  `r e0 :m(e1 :::en ) : RetT (meth) k ([ETi )ni=0 [ ExcT (meth) P V P T0i

` v Throwable ; ;  `r e : E k ET; e 6=  ; ;  `r throw e : void k fEg [ ET

P E P V P V

 ` v ; 2f ::: g ; ; ` void k ; 2f ::: g ; ; ` void k ; ; ` ; ;  ` try catch ::: catch void k n f ; : : : ; g [ [ ; ;  ` try catch ::: catch void k n f ; : : : ; g [ [

n 0 P Ei Throwable i 1 n r P V; Ei vi stmtsi : ETi i 1 n r r P V stmts0 : ET0 OR (P V stmts0 : E-Thrn AND ET0 r P V stmts0 E1 v1 stmts1 En vn stmtsn : (ET0 eP E1 En ) ( ETi )ni=1 r P V stmts0 E1 v1 stmts1 En vn stmtsn n+1 e (ET0 P E1 En ) ( ETi )i=1

` E v Throwable  () = :::E P; V;  `r throw  :

= ;)

nally stmts + :

@A is a context ; ;  `r t : E-Thrn ; ;  `r cont @ t A : E-Thrn

cont P V P V

P

E-Thrn

Figure 5: Types for some Javar terms

6 The Java type system r

We saw in the previous section that an exception term may continue execution abnormally only, whereas an exception-free term may continue execution normally or abnormally. Therefore, an exception term has a thrown type E-Thrn where E is the class of the thrown exception object. Also, an exception-free term has a normal type which characterizes its normal execution, and an abnormal type which characterizes its abnormal execution. Normal types are primitive types, interfaces, classes, or void; these are denoted by T, T0 , etc. Abnormal types are sets of subclasses of the prede ned class Throwable; those are denoted by ET, ET0 , etc., or sets like fE1 ; : : : ; Eq g. For example, Illness-Thrn and NullPE-Thrn are thrown types; Illness, Person, void are normal types; and nally, fIllness, Worryg, fNullPointerException, 9

n 1

g are abnormal types.5 The type of an address  depends on the object or array it points at in the current state . Therefore, Java type judgements take states  into account, and

Worry

r

have the form

; ;  `r t : E-Thrn ; ;  `r t : T k ET

for exception term t, and for exception-free term t, for normal type T, abnormal type ET, program P, and environment V mapping variables to their types. The type T stands for all types, whether of the form E-Thrn or of the form T k ET. P V P V

In gure 5 we show some of the type rules for Javar terms.6 The rst ve rules describe exception-free terms, and the last two rules describe exception terms. We rst examine the normal types of the exception-free terms. If an object is stored at address , i.e. () = :::C , then its class C is the normal type of . An assignment is type correct, and has normal type void, if its right-hand side has a type which widens (is a subclass) of that of the left-hand side. The normal type of a method call is the return type of the method that ts the method descriptor T1  :::  Tn stored with the method call, provided that the arguments have types that can be widened to the corresponding argument types in the method descriptor. A throw statement has normal type void, provided that E, the normal type of the expression e, is an exception class (i.e. a subclass of Throwable). Finally, a trycatch statement has normal type void, provided that all classes mentioned in the catch clauses are exception classes, and that the catch clauses are well-typed. Similar requirements for try-catch- nally statements. We next examine the abnormal types of the exception-free terms. The abnormal type of an address  is empty { no consistency can be violated. In most cases, the abnormal type of a term is the union of the abnormal types of its subterms. For instance, the abnormal type of an assignment consists of the abnormal types of the left-hand side and the right-hand side. The abnormal type of a method call consists of the union of the abnormal types of the receiver, the arguments, and the abnormal type declared in the throws part of the method header (ExcT (methH)). The abnormal type of a throw statement is the union of the normal type (E) and the abnormal type (ET) of the expression e that follows it. Namely, if evaluation of e succeeds, then the throw statement will throw an exception compatible with E; if evaluation of e throws an exception compatible with ET, then the throw statement will throw an exception compatible with ET. Finally, the abnormal type of a trycatch statement is the abnormal type of the try part from which we exclude the exception classes from the catch clauses, united with the abnormal types of the catch clauses. Similar requirements for try-catch- nally statements. The relations e and neP are the set-theoretic subset and di erence, taking subclasses into account, i.e.  P ` ET e ET0 i 8E 2 ET : P ` E v Error; or P ` E v RTE; or 9E0 2 ET0 : P ` E v E0  ET0 neP ET = f E0 j E0 2 ET0 and 8E 2 ET : : P ` E0 v E g The last two rules in gure 5 describe the types of exception terms. These are thrown types, E-Thrn, where E is the class of the object being thrown. Returning to our example, and taking

Vwr

=

Person peter

and a state  with

Compared with [20], normal types correspond to the sets Xe , abnormal types correspond to the sets Pe . 6 In our current work, in order to model separte compilation we de ne typings in terms of declarations D derived from programs rather than the programs themselves. For simplicity of presentation we dropped the distinction between D and P in this paper 5

10

() = :::Illness , we obtain the following typings: Pwr ; Vwr ;  `r peter.[]diagnose() : Illness k fg Pwr ; Vwr ;  `r throw peter.[]diagnose() : void k fIllnessg Pwr ; Vwr ;  `r peter.[]diagnose().[]treat() : Illness k fWorryg Pwr ; Vwr ;  `r throw peter.[]diagnose().[]treat() : void k fWorry; Illnessg Pwr ; Vwr ;  `r peter.[]act() : void k fWorry; Illnessg Pwr ; Vwr ;  `r peter.[]study() : void k fg, Pwr ; Vwr ;  `r throw  : Illness-Thrn Pwr ; Vwr ;  `r (throw ):act() : Illness-Thrn Pwr ; Vwr ;  `r try (throw ):act()::: catch::: : void k f:::g

7 Widening and subject reduction

Widening, w , is de ned for all types; some rules are given in gure 6. Widening for normal types corresponds to subtyping. A normal type widens to another normal type if they are identical, or if the rst is a subclass or subinterface of the latter. Widening for normal types is required for assignments and for parameter passing. For example, Pwr

` Doctor w

Person

which makes the assignment peter=david; type-correct. A normal type-abnormal type pair widens to another type if the normal type of the rst widens to the normal type of the latter, and if every class in the abnormal type of the rst is an unchecked exception (i.e. subclass of Error or RunTimeException) or has a superclass in the abnormal type of the latter. The type E-Thrn widens to itself, and E-Thrn widens to another type if E is an unchecked exception or E has a superclass in the abnormal type of the latter. Widening for normal type-abnormal type pairs and for thrown types is used to express subject reduction.7 P P

::: P P P

` T  w T0 ` ET e ET0 ` T k ET w

T0

k ET0

P P

` T v T0 ` T w

:::

T0

` fEg e ET ` E-Thrn w T k ET

P

` E-Thrn w

E-Thrn

Figure 6: Widening { some rules Applying these rules to our example: Pwr Pwr Pwr

` Illness k fg w Illness k fWorryg, ` NullPE-Thrn w Illness k fWorryg, ` Worry-Thrn w void k fWorry; Illnessg.

Note: Types E and E-Thrn are incompatible, because E indicates an object of class E which has not been thrown, whereas E-Thrn indicates a term that contains a thrown exception of class E. Note: Types E-Thrn and E0 -Thrn with di erent E and E0 are incompatible, even if E is a subclass of E0 .

7 In systems where a term is allowed to throw any exception the situation is much simpler, and abnormal types need not be considered. A raise expression may have any type  in [10]. In [2, 1], where we had not yet considered the requirement to declare exceptions in throws clauses, the type E-Thrn widens to any normal type.

11

We can now see that the reason for the absence of rewrite rules of the form e;  ;throw new E();  is the requirment for execution to be type-preserving. Namely, the term throw new E() has the type void k fEg which does not necessarily widen to the type of e. But the type of throw  does widen to the type of e, if  points to an object of appropriate exception class. P

Execution is well-de ned only if some conformance requirements are satis ed. We outline these requirements here, more can be found in [3]. A state  conforms to P and V, i.e. P; V `  3, if all variables and addresses of  contain values which conform to the variable type expected in V and P, e.g. objects are required to be constructed according to their classes, etc. An environment V conforms to an environment V0 , i.e. V  V0 , if V contains all variable declarations from V0 . Finally, a con guration t; conforms to type T, i.e. P; V ` t;  : T3, if P; V `  3 and P; V;  `r t : T. For subject reduction we also need the notion of complete program, `b P 3, which guarantees that the Javab program P satis es all the well-formedness requirements of Java and that it is complete, i.e. all the necessary classes have been linked and checked. More in appendix A1. The subject reduction theorem states that execution of a term either terminates in a new state, or it produces a new term whose type widens to that of the original. Therefore, the theorem guarantees that normal execution returns a value compatible with the normal type of the original term, and abnormal execution terminates with an exception compatible with the abnormal type of the term.

Theorem 1 Subject Reduction For Java program P with ` P 3, environment V, state  , non-ground Java term t, type T with P; V ` t;  : T3 b

b

r

either

 9 0 , V0 , t0 , T0 such that: { t; ;t0; 0 and { V0  V, and { P; V0 ` t0; 0 : T03 and P

or

 90 , ET : t; ;0 and P

P,V

P

` T0 w T

` 0 3 and T = void k ET

Going again to our example, the subject reduction theorem and the typing Pwr ; Vwr ;  10 `r peter.[]act() : void k fWorry; Illnessg guarantee that execution of the term peter.[]act() in the state 10 will either terminate successfully or throw an exception of class Worry or Illness (or a subclass), or throw an unchecked exception such as NullPointerException or OutOfMemoryError. In appendix A2 we follow three possible execution paths, one where no exception is thrown, one where an Illness exception thrown and caught, and one where a Worry exception thrown, and demonstrate that in all cases the nal state conforms to the program and the environment.

8 Conclusions, Comparisons and Further Work To our knowledge, our work is the rst to model exceptions for such a large subset of Java, and demonstrate the soundness of the Java approach. In [11, 1, 10] operational semantics and type systems for Java and SML exceptions are developed where method types do not mention the exceptions potentially escaping their bodies. 12

The formal system we have developed is very near to Java and to programmers' intuitive ideas about program compilation and execution. So far we have only outlined the proofs of the lemmas and theorem. The work would bene t from the use of a theorem prover, as e.g. in [17, 15]. Modelling Java exceptions and, in particular, the dual role they play whereby they may be used as any object in normal execution until explicitly thrown was quite challenging. Formalization of language features leads to better understanding; for example, the distinction between exception terms and exception-free terms, and their properties, as well as our succinct description of the operational semantics of exception propagation and handling, etc. On a related topic, the inference of the throws clauses { as opposed to simple checking as in Java { has attracted great interest. The precision of that information has considerable practical implications: An over-cautious programmer, who declares too many exceptions in throws clauses, makes his methods more heavyweight to call. A tool which analyzes exception ow in Java source code [13], (and, in particular, detects exceptions handled through subsumption) demonstrated that commercial packages tend to handle exceptions at too coarse a grain. A constraint system [20] gives an exception ow analysis algorithm independent of the programmer's speci cations. This problem has also attracted research in languages where the counterpart of throws clauses does not exist. Control- ow analysis and effects systems have been applied for the estimation of uncaught exceptions in SML programs [21, 12]. Exceptions introduce the potential for non-determinism. For example, the expression e1 and e2 is not equivalent to e2 and e1 , because e1 and e2 might raise di erent exceptions, one of which might be caught. Such considerations could restrict language implementations, i.e. expressions could not be re-arranged unless it can be proven that they raise no exceptions. In [7] a semantics dealing with imprecise exceptions based on the IO monad in Haskell is suggested. This approach is not directly applicable to Java because of the presence of the IO monad, but the issue it tackles is applicable to any language. The question of imprecise exceptions becomes even more pertinent for Java, because the language deliberately under-speci es the time and exact nature of loading and linkage-related errors, although it does place some constraints, e.g. ch. 12.1.2 in [5]. A semantics for this part of the Java language, which would characterize a whole family of non-deterministic implementations and their properties, is an interesting open task. Finally, exceptions are intimately related to the Java dynamic linker-loader, e.g. the linkage exceptions NoClassDefFoundError, IncompatibleClassChangeError, etc. A more accurate model of the Java dynamic linker-loader, based e.g. on [18, 6, 8], would complete the picture.

Acknowledgments This paper is an account of the treatment of exceptions in [3]. We received valuable feedback from Susan Eisenbach, Mark Skipper and David Wragg.

References [1] Sophia Drossopoulou and Susan Eisenbach. Towards an Operational Semantics and Proof of Type Soundness for Java. In Jim Alves-Foss, editor, Formal Syntax and Semantics of Java, LNCS. Springer, 1998. Chapter 3. 13

[2] Sophia Drossopoulou, Susan Eisenbach, and Sarfraz Khurshid. Is the Java Type System Sound? Theory And Practice of Object Systems, 5(1):3{24, 1999. [3] Sophia Drossopoulou, Tanya Valkevych, and Susan Eisenbach. Java Type Soundness Revisited. Technical report, Imperial College, November 1999. Also available from: http://www-doc.ic.ac.uk/~scd. [4] John B. Goodenough. Exception handling: Issues and proposed notation. Communications of the ACM, 18(12), December 1975. [5] James Gosling, Bill Joy, and Guy Steele. The Java Language Speci cation. Addison-Wesley, August 1996. [6] Thomas Jensen, Daniel Le Metyayer, and Tommy Thorn. A Formalization of Visibility and Dynamic Loading in Java. In IEEE ICCL, 1998. [7] Simon Peyton Jones, Alastair Reid, Tony Hoare, Simon Marlow, and Fergus Henderson. A semantics for imprecise exceptions. In Proceedings of the SIGPLAN Symposium on Programming Language Design and Implementation, May 1999. [8] Sheng Liang and Gilad Bracha. Dynamic Class Loading in the JavaTM Virtual Machine. In OOPSLA Proceedings, October 1998. [9] John M. Lucassen and David Gi ord. Polymorphic E ect Systems. In POPL'88 Proceedings, January 1988. [10] Robin Milner, Mads Tofte, and David MacQueen. The De nition of Standard ML (Revised). MIT Press, 1997. [11] Tobias Nipkow and David von Oheimb. Java`ight is Type-Safe | De nitely. In POPL Proceedings, 1998. [12] Francois Pessaux and Xavier Leroy. Type-based analysis of uncaught exceptions. In Proceedings of 26th ACM Conference on Principles of Programming Languages, January 1999. [13] Martin P. Robillard and Gail C. Murphy. Analyzing Exception Flow in Java Programs. In Proceedings of the European Software Engineering ConferenceFoundations of Software Engineering, September 1999. [14] Bjarne Strousrup. The C++ Programming Language. Addison Wesley, 1991. [15] Donald Syme. Proving Java Type Sound. In Jim Alves-Foss, editor, Formal Syntax and Semantics of Java, LNCS. Springer, 1998. Chapter 4. [16] Jean-Pierre Talpin and Pierre Jouvelot. Polymorphic Type, Region and E ect Inference. Functional Programming, 2(3):245{271, July 1992. [17] David von Oheimb and Tobias Nipkow. Machine-checking the Java Speci cation: Proving Type-Safety. In Jim Alves-Foss, editor, Formal Syntax and Semantics of Java, LNCS. Springer, 1998. Chapter 5. [18] Joe Wells and Rene Vestergaard. Con uent Equational Reasoning for Linking with First-Class Primitive Modules. Technical report, Herriot-Watt University, August 1999. Also available from: http://www.cee.hw.ac.uk/jbw/papers/. [19] Andrew Wright and Matthias Felleisen. A Syntactic Approach to Type Soundness. Information and Computation, 115(1), 1994. 14

[20] Kwangkeun Yi and Byeong-Mo Chang. Exception Analysis for Java. In Bart Jacobs, Gary T. Leavens, Peter Mueller, and Arnd Poetzsch-He ter, editors, Proceedings of the ECOOP-Workshop on Formal Techniques for Java Programs, June 1999. [21] Kwangkeun Yi and Sukyoung Ryu. Towards a cost-e ective estimation of uncaught exceptions in SML programs. In Proceedings of the 4th International Static Analysis Conference, volume 1302 of Lecture Notes in Computer Science. Springer Verlag, 1997.

Appendix

A1. Well-formed and complete Javab programs

We outline the requirements that make Javab program well-formed and complete, concentrating on the issues around exceptions. The requirements are formalized in gure 7. A Javab program is well-formed and complete, i.e. `b P 3, if it is well-formed in the context of its declarations, i.e. if D(P) ` P 3. P is well-formed in the context of some declarations D, i.e. D ` P 3, if the declarations of P are well-formed, i.e. D ` D(P) 3, and if all class de nitions in P are well-formed in D, i.e. if D `b CDef (P; C) 3 for all C de ned in P. Well-formedness of class de nitions requires all methods meth=T m(T1 p1 ,:::,Tn pn ) throws E1 ,:::,Eq fmBodyg to be well-formed, and the method body mBody to be of type T0 k ET0 where T0 widens to the declared return type T, and ET0 consists of unchecked exceptions or subclasses of those declared in the throws clause. These two requirements are formalized as D ` T0 k ET0 w T k fE1; : : : ; Eqg. Well-formedness of declarations imposes several restrictions, e.g. unique declarations of classes, interfaces and elds, i.e. ` D 3u, acyclic subclass and subinterface hierarchies, i.e. D0 ` D 3a. With respect to exceptions it requires the throws clause of any method header methH to consist of unchecked exceptions or of subclasses of the throws clause of any method header methH0 it overrides. In other words (c.f. gure 7, the fth rule, last requirement), if methH 2 MDecl (D; I; m) { i.e. methH belongs to interface I { and methH0 2 Meths (D; I0 ; m) with I0 2 Interfs (D; I) and ArgT (methH) = ArgT (methH0) { i.e. methH0 is declared in a superinterface and has the same identi er and argument type as methH and therefore is overridden by methH { then ExcT (methH) e ExcT (methH0 ). Similar requirements are for classes (C) and their methods (methH 2 MDecl (D; C; m)) in the last rule of gure 7.

Appendix

A2. Example Execution

As an example of our subject reduction consider program Pwr and the term peter.[]act() in a state 10 , where 10 (peter) = 11 10 (11 ) = age Person : 0 Person Disregarding the possibility for unchecked exceptions like null dereferencing, etc., there are three possible outcomes, namely: 11 { no exception thrown, 14 { an Illness exception thrown and caught, throw 13; 13 { a Worry exception thrown. where the states are: 15

D(P) ` P 3 `P3 b

` D(P) 3 8C CDef (P; C) 6= Undef =) D` P 3

D

D

b

` CDef (P; C) 3 b

2 MDef (P; C; m) with meth = T m(T p ,:::,T p ) throws E ,:::,E fmBodyg D ` C this; T p ; :::; T p ; 3 9T0 ; 9ET0 D; C this; T p ; :::; T p ; ` mBody : T0 k ET0 and D ` T0 k ET0 w T k fE ; : : : ; E g D ` CDef (P; C) 3 meth

1

=)

1

1

n

n

1

1

n

n

1

n

n

1

q

b

1

b

` 3 ` 3 8 ; 6 8 ; 6 ` 3

D u D0 D a I IDecl (D I) = Undef C CDecl (D C) = Undef D0 D

q

=) D0 ` IDecl (D; I) 3 =) D0 ` CDecl (D; C) 3

I0 Interfs (D I) = D I0 I0 methH MDecl (D I m) = D RetT (methH) NorType D ArgT (methH) ArgType D ExcT (methH) AbnType methH1 MDecl (D I m) and methH2 MDecl (D I m) and methH1 = methH2 = ArgT (methH1) = ArgT (methH2) m I0 Interfs (D I) methH MDecl (D I m) methH0 Meths (D I0 m) ArgT (methH) = ArgT (methH0 ) = RetT (methH) = RetT (methH0) D ExcT (methH) e ExcT (methH0) D IDecl (D I)

2

)

2

) ; ;

;

` ` `

2 ; ; ) 6 8 ;8 2 ; ; 2 ; ; ; ) ` ; 3

` 

3 3 3

; ;

2

2

; `

6

; ;

; ; ;



C0 = SuperC (D C) = D C0 C0 I Interfs (D C) = D I I T f = FDecl (D C f) = D T V arType methH MDecl (D C m) methH = T m T1 p1 Tn pn E1 Es = D T NorType D T1 Tn ArgType D E1 Es AbnType methH1 MDecl (D C m) and methH2 MDecl (D C m) and methH1 = methH2 = ArgT (methH1) = ArgT (methH2) m methH MDecl (D C m) methH0 Meths (D C0 m) ArgT (methH) = ArgT (methH0 ) = RetT (methH) = RetT (methH0) D ExcT (methH) e ExcT (methH0) m I Interfs (D C) methH1 MDecl (D I m) = methH2 Meths (D C m) ArgT (methH1) = ArgT (methH2 ) RetT (methH1) = RetT (methH2) D ExcT (methH2) e ExcT (methH1) D CDecl (D C)

) ` v ) `  ; ; ) ` 3 2 ; ; with ( ,:::, ) ` 3 ; `  :::  3 ; ` f ;:::; g 3 2 ; ; 2 ; ; ) 6 8 2 ; ; ; 2 ; ; ; ) ; ` 8 ;8 2 ; ; 2 ; ; ) 9 2 ; ; ; ; ` ` ; 3 2

;

;

) throws ,:::, ; 6



Figure 7: Well-formed and complete Javab programs

16



;

= 10 [11 7!age Person : 1 Person ] = 10 [12 7!severity Illness : 0 Illness] = 12 [13 7!Worry ] = 12 [12 7!severity Illness : ?10 Illness] The following table demonstrates execution of the term peter.[]act() in more detail. We group consecutive executions in marked blocks. At the end of block 1, depending on the outcome of the condition, execution may continue at block 2 or 3. Similarly, at the end of block 3 execution may continue at block 4 or 5.

11 12 13 14

.

(); 10

peter []act

Block 1 { start

;  .[]act(); 10 ; try f if (:::) throw diagnose().[]treat(); else age=age + 1; g catch( Illness i)f i.[]cure(); g; 10 ; continue at 2) or 3) Pwr

11

Pwr

Pwr

Block 2 { the condition returns false try fage=age + 1; g catch( Illness i)f i.[]cure(); g; 10

; try fage=1; g catch( Illness i)f i.[]cure(); g; 10 ; 11 Pwr

Pwr

Block 3 { the condition returns true try f throw diagnose().[]treat(); g

catch( Illness i)f i:cure(); g; 10 ; try f throw  ().[]treat(); g catch( Illness i)f i.[]cure(); g; 12 ; try f throw (if (:::) throw new Worry(); else return this;)g catch( Illness i)f i.[]cure(); g; 12 ; continue at 4) or 5) Pwr

12

Pwr

Pwr

Block 4 { the condition returns false

try f throw this;g catch( Illness i)f i.[]cure(); g; 12 ; try f throw  ;g catch( Illness i)f i.[]cure(); g; 12 ;  .[]cure();; 12 ; Pwr

12

Pwr

12

Pwr

14

Block 5 { the condition returns true try f throw (throw new Worry();)g catch( Illness i)f i.[]cure(); g; 12

; try f throw (throw  ;)g catch( Illness i)f i.[]cure(); g; 13 ; try f throw  ;g catch( Illness i)f i.[]cure(); g; 13 ; throw  ; 13 Pwr

13

Pwr

13

Pwr

13

Indeed, as stated in the subject reduction theorem (in section 7), for Vwr and Pwr the nal states describing the three possible outcomes of the term rewriting we have: Pwr ,Vwr `  11 3, Pwr ,Vwr `  14 3, Pwr ; Vwr ` throw 13 ;  13 : Worry-Thrn3 and Pwr ` Worry-Thrn w void k fWorry; Illnessg.

17