On a New Method for Dataow Analysis of Java Virtual Machine Subroutines Masami Hagiya and Akihiko Tozawa Department of Information Science, Graduate S

Similar documents
1 Introduction One of the contributions of Java is in its bytecode verier, which checks type safety of bytecode for JVM (Java Virtual Machine) prior t

A Type System for Object Initialization In the Java TM Bytecode Language

A Type System for Java Bytecode Subroutines

A.java class A f void f() f... g g - Java - - class file Compiler > B.class network class file A.class Java Virtual Machine Loa

void f() { try { something(); } finally { done(); } } Figure 1: A method using a try-finally statement. system developed by Stata and Abadi [SA98a]. E

Natural Semantics [14] within the Centaur system [6], and the Typol formalism [8] which provides us with executable specications. The outcome of such

that programs might be downloaded and executed without users realizing that this is happening. Users are keen to take advantage of the new functionali

Java Internals. Frank Yellin Tim Lindholm JavaSoft

1 Introduction An exciting recent development in program verication is the notion of certied code. When downloading executable code from an untrusted

Java byte code verification

An Approach to the Generation of High-Assurance Java Card Applets

16 Greedy Algorithms

SORT INFERENCE \coregular" signatures, they derive an algorithm for computing a most general typing for expressions e which is only slightly more comp

arxiv: v3 [cs.ds] 18 Apr 2011

Problems of Bytecode Verification

Provably Correct Software

2 Egon Borger, Wolfram Schulte: Initialization Problems for Java 1 class A implements I{ } 2 3 interface I { static boolean dummy = Main.sideeffect =

Combining Analyses, Combining Optimizations - Summary

Operational Semantics

CS4215 Programming Language Implementation. Martin Henz

time using O( n log n ) processors on the EREW PRAM. Thus, our algorithm improves on the previous results, either in time complexity or in the model o

perform. If more storage is required, more can be added without having to modify the processor (provided that the extra memory is still addressable).

Agenda. CSE P 501 Compilers. Java Implementation Overview. JVM Architecture. JVM Runtime Data Areas (1) JVM Data Types. CSE P 501 Su04 T-1

A Nim game played on graphs II

Project 3 Due October 21, 2015, 11:59:59pm

Practical Java Card bytecode compression 1

Optimum Alphabetic Binary Trees T. C. Hu and J. D. Morgenthaler Department of Computer Science and Engineering, School of Engineering, University of C

In Our Last Exciting Episode

CSE P 501 Compilers. Java Implementation JVMs, JITs &c Hal Perkins Winter /11/ Hal Perkins & UW CSE V-1

The security mechanisms of Java

On Meaning Preservation of a Calculus of Records

ALGORITHMIC DECIDABILITY OF COMPUTER PROGRAM-FUNCTIONS LANGUAGE PROPERTIES. Nikolay Kosovskiy

1. true / false By a compiler we mean a program that translates to code that will run natively on some machine.

JAM 16: The Instruction Set & Sample Programs

Outline. Proof Carrying Code. Hardware Trojan Threat. Why Compromise HDL Code?

CITS5501 Software Testing and Quality Assurance Formal methods

CS 3410 Ch 7 Recursion

CMa simple C Abstract Machine

Deep type inference for mobile functions

C. E. McDowell August 25, Baskin Center for. University of California, Santa Cruz. Santa Cruz, CA USA. abstract

COS 320. Compiling Techniques

Thunks (continued) Olivier Danvy, John Hatcli. Department of Computing and Information Sciences. Kansas State University. Manhattan, Kansas 66506, USA

A Combined BIT and TIMESTAMP Algorithm for. the List Update Problem. Susanne Albers, Bernhard von Stengel, Ralph Werchner

Heap-on-Top Priority Queues. March Abstract. We introduce the heap-on-top (hot) priority queue data structure that combines the

the application rule M : x:a: B N : A M N : (x:a: B) N and the reduction rule (x: A: B) N! Bfx := Ng. Their algorithm is not fully satisfactory in the

Language Techniques for Provably Safe Mobile Code

Mechanized Operational Semantics

Chapter 3. Describing Syntax and Semantics

QUTE: A PROLOG/LISP TYPE LANGUAGE FOR LOGIC PROGRAMMING

Static Program Analysis

Code Generation. The Main Idea of Today s Lecture. We can emit stack-machine-style code for expressions via recursion. Lecture Outline.

(Preliminary Version 2 ) Jai-Hoon Kim Nitin H. Vaidya. Department of Computer Science. Texas A&M University. College Station, TX

We can emit stack-machine-style code for expressions via recursion

CS2 Algorithms and Data Structures Note 10. Depth-First Search and Topological Sorting

to automatically generate parallel code for many applications that periodically update shared data structures using commuting operations and/or manipu

STABILITY AND PARADOX IN ALGORITHMIC LOGIC

2386 IEEE TRANSACTIONS ON INFORMATION THEORY, VOL. 52, NO. 6, JUNE 2006

Global Scheduler. Global Issue. Global Retire

javac 29: pop 30: iconst_0 31: istore_3 32: jsr [label_51]

Parallelism of Java Bytecode Programs and a Java ILP Processor Architecture

1.3. Conditional expressions To express case distinctions like

1 Lexical Considerations

Handout 9: Imperative Programs and State

Modal Logic: Implications for Design of a Language for Distributed Computation p.1/53

The Compositional C++ Language. Denition. Abstract. This document gives a concise denition of the syntax and semantics

University of Utrecht. 1992; Fokker, 1995), the use of monads to structure functional programs (Wadler,

Parallel Program Graphs and their. (fvivek dependence graphs, including the Control Flow Graph (CFG) which

Lecture Notes on Contracts

JPred-P 2. Josh Choi, Michael Welch {joshchoi,

CS4215 Programming Language Implementation

Byzantine Consensus in Directed Graphs

Let us dene the basic notation and list some results. We will consider that stack eects (type signatures) form a polycyclic monoid (introduced in [NiP

Adam Chlipala University of California, Berkeley ICFP 2006

THE FREUDENTHAL-HOPF THEOREM

Identity-based Access Control

Compiling Techniques

A Simplied NP-complete MAXSAT Problem. Abstract. It is shown that the MAX2SAT problem is NP-complete even if every variable

Towards a formal model of object-oriented hyperslices

original term canonical completion minimal d.t. completion original term canonical completion minimal d.t. completion minimal s.c. completion (x:x) (y

Algebraic Properties of CSP Model Operators? Y.C. Law and J.H.M. Lee. The Chinese University of Hong Kong.

/633 Introduction to Algorithms Lecturer: Michael Dinitz Topic: Priority Queues / Heaps Date: 9/27/17

COMP-421 Compiler Design. Presented by Dr Ioanna Dionysiou

The Encoding Complexity of Network Coding

Super-Key Classes for Updating. Materialized Derived Classes in Object Bases

CS 164 Handout 16. Final Examination. There are nine questions on the exam, some in multiple parts. You have 3 hours to work on the

CMPSCI 250: Introduction to Computation. Lecture #14: Induction and Recursion (Still More Induction) David Mix Barrington 14 March 2013

Lecture 2 - Graph Theory Fundamentals - Reachability and Exploration 1

Localization in Graphs. Richardson, TX Azriel Rosenfeld. Center for Automation Research. College Park, MD

COMPOSABILITY, PROVABILITY, REUSABILITY (CPR) FOR SURVIVABILITY

Implementation of Hopcroft's Algorithm

Framework for Design of Dynamic Programming Algorithms

Reading 1 : Introduction

Symbolic Execution and Proof of Properties

Programs with infinite loops: from primitive recursive predicates to the arithmetic hierarchy

Draw a diagram of an empty circular queue and describe it to the reader.

ER E P M S S I TRANSLATION OF CONDITIONAL COMPIL DEPARTMENT OF COMPUTER SCIENCE UNIVERSITY OF TAMPERE REPORT A

Lecture 1 Contracts. 1 A Mysterious Program : Principles of Imperative Computation (Spring 2018) Frank Pfenning

Field Analysis. Last time Exploit encapsulation to improve memory system performance

Lexical Considerations

Propositional Logic. Part I

Transcription:

On a New Method for Dataow Analysis of Java Virtual Machine Subroutines Masami Hagiya (corresponding author) and Akihiko Tozawa Department of Information Science, Graduate School of Science, University of Tokyo, 7-3-1 Hongo, Bunkyo-ku, Tokyo 113-0033, JAPAN tel: +81-3-3812-4177 fax: +81-3-3818-1073 email: hagiya@is.s.u-tokyo.ac.jp Abstract. The bytecode verier of Java Virtual Machine, which statically checks type safety of Java bytecode, is a basis of the security model of Java for guaranteeing safety of mobile code sent from an untrusted remote host. However, the type system for Java bytecode has some technical problems, one of which is in the handling of subroutines. Based on the work of Stata and Abadi and that of Qian, this paper presents yet another type system for subroutines of Java Virtual Machine. Our type system includes types of the form last(x). A value whose type is last(x) is the same as that of the x-th variable of the caller of the subroutine. In addition, we represent the type of a return address by the form return(n), which means returning to the n-th outer caller. By virtue of these types, we can analyze instructions purely in terms of types. As a result, the correctness proof of bytecode verication becomes extremely simple. Moreover, for some programs, our method turns out to be more powerful than existing ones. In particular, our method has no restrictions on the number of entries and exits of a subroutine. Keywords: Java, virtual machine, bytecode verication, dataow analysis, type system

On a New Method for Dataow Analysis of Java Virtual Machine Subroutines Masami Hagiya and Akihiko Tozawa Department of Information Science, Graduate School of Science, University of Tokyo fhagiya,milesg@is.s.u-tokyo.ac.jp Abstract. The bytecode verier of Java Virtual Machine, which statically checks type safety of Java bytecode, is a basis of the security model of Java for guaranteeing safety of mobile code sent from an untrusted remote host. However, the type system for Java bytecode has some technical problems, one of which is in the handling of subroutines. Based on the work of Stata and Abadi and that of Qian, this paper presents yet another type system for subroutines of Java Virtual Machine. Our type system includes types of the form last(x). A value whose type is last(x) is the same as that of the x-th variable of the caller of the subroutine. In addition, we represent the type of a return address by the form return(n), which means returning to the n-th outer caller. By virtue of these types, we can analyze instructions purely in terms of types. As a result, the correctness proof of bytecode verication becomes extremely simple. Moreover, for some programs, our method turns out to be more powerful than existing ones. In particular, our method has no restrictions on the number of entries and exits of a subroutine. 1 Introduction One of the contributions of Java is in its bytecode verier, which statically checks type safety of bytecode for JVM (Java Virtual Machine) prior to execution. Thanks to the bytecode verier, bytecode sent from an untrusted remote host can be executed without the danger of causing type errors and destroying the entire security model of Java, even when source code is not available. Verifying type safety of bytecode (or native code) seems to be a new research area that is not only technically interesting but also practically important due to availability of remote binary code in web browsers and other applications. Much eort has been made for guaranteeing security of Java programs including type safety of bytecode: { Security model for Java applets: The security model for Java applets is said to consist of three prongs: the bytecode verier, the applet class loader and the security manager [9]. In this model, the bytecode verier plays the most fundamental role, on which the other two prongs are based. If the bytecode verier is cheated, the other two also become ineective. The applet

class loader dynamically loads classes into an applet. The security manager explicitly controls accesses from within applets. { Type safety of source code: The type safety of Java programs has been proved, either formally (using a theorem proving assistant), or rigorously (but not formally) [3, 4, 17, 12]. This means that a program that has passed static type checking is guaranteed to cause no type errors while it is running. For example, Nipkow and von Oheimb formally proved the type safety of a subset of Java, called Bali, using Isabelle/HOL [12]. { Safety of class loading: Java allows loading classes lazily, i.e., only when classes are actually accessed. In order to support various loading disciplines, Java allows programmers to dene their own class loaders. This has opened one of the security holes of Java [14]. To avoid such a security hole, Dean formalized part of the class loader functionality and formally proved its correctness using PVS [2]. Drossopoulou et al. recently formulated the concept of binary compatibility on which class loading is based [18]!%Goldberg's main concern is also class loading, though he copes with bytecode verication [5]. { Type safety of bytecode: The bytecode verier statically checks type safety of Java bytecode. If the bytecode verier accepts incorrectly typed bytecode, it will break the entire security model of Java. It guarantees that no type error occurs at each instruction by performing dataow analysis on bytecode. Besides researches on each aspect of security as mentioned above, there are some on-going projects, which are developing network programming environments that are more secure than existing ones [7, 15]. This paper is concerning bytecode verication. Since it is a basis of the entire security model of Java, it is desirable to rigorously prove that if a bytecode program has passed bytecode verication, it never causes a runtime type error. However, the specication of the bytecode verier is written in an ambiguous natural language (i.e., English) [8]. In order to be able to show the correctness of the bytecode verier, one has to begin with formally specifying the operational semantics of the virtual machine and then give the formal specication of the bytecode verier. Cohen described the operational semantics of JVM in the input language of ACL2 (the most recent descendant of Boyer and Moore's prover) [1]. The operational semantics dened there is called defensive and detects type errors each time an instruction is executed. Therefore, it is supposed that this work is aimed at proving the correctness of the bytecode verier. Qian rigorously dened the operational semantics of a subset of JVM and formulated the bytecode verier as a type system [13]. He then succeeded in proving the correctness of the bytecode verier, though not completely. Bytecode verication of JVM has some technical challenges. One is that of handling object initialization, where objects created but not initialized yet may open a security hole. In Qian's work, much attention is paid to the handling of object initialization. Another is that of handling polymorphism of subroutines. This paper is on this issue. Inside a JVM subroutine, which is a result of compiling a finally

clause in a try statement, local variables may have values of dierent types depending on a caller of the subroutine. This is a kind of polymorphism. For investigating analysis of JVM subroutines, Stata and Abadi dened a type system for a small subset of JVM and proved its correctness with respect to the operational semantics of the subset [16]. Qian's system is similar to Stata and Abadi's in the handling of subroutines [13]. Both the systems faithfully follow the specication of the bytecode verier, and make use of the information on which variables are accessed or modied in a subroutine. Those variables that are not accessed or modied are simply ignored during analysis of the subroutine. This paper takes a dierent approach. We introduce types of the form last(x). A value whose type is last(x) is the same as that of the x-th variable of the caller of the subroutine. In addition, we represent the type of a return address by the form return(n), which means returning to the n-th outer caller. Our approach has the following advantages. { By virtue of last and return types, we can analyze instructions purely in terms of types. As a result, the correctness proof of bytecode verication becomes extremely simple. We do not need separate analysis on variable access or modication. { For some programs (unfortunately, not those produced by the Java compiler), our method turns out to be more powerful than existing ones. In particular, our method has no restrictions on the number of entries and exits of a subroutine. Due to these advantages, we hope that our method can be modied and applied to analysis of other kinds of bytecode or native code [10, 11]. This paper is organized as follows. In the next section, we explain more on JVM subroutines and our approach to bytecode verication. In Section 3, a subset of JVM, which is similar to Stata and Abadi's, is dened. In Section 4, our method for analysis of bytecode is described and its correctness is shown. In Section 5, issues on implementation are briey discussed. Section 6 is for concluding remark. 2 Analysis of Subroutines JVM is a classical virtual machine consisting of { an array for storing values of local variables, { a stack for placing arguments and results of operators, called the operand stack, { a heap for storing method code and object bodies, and { a stack for frames. To allow checking type safety of bytecode, it prepares dierent instructions for the same operation depending on the type of the operands. For example, it has the instruction istore for storing integers and fstore for storing oating-point numbers.

JVM subroutines are mainly used for compiling finally clauses of try statements of Java. Notice that subroutine calls are completely dierent from method calls. A subroutine is locally dened inside a method. In contrast to methods, it is not allowed to call subroutines recursively. In this paper, we dene a virtual machine based on JVML0, which was formulated by State and Abadi for investigating analysis of JVM subroutines. Subroutines are called by instructions of the following form. jsr(l) An instruction of this form pushes the address of its next instruction (i.e., the return address) onto the operand stack and jumps to the subroutine L. Subroutines are usually dened as follows. L : store(x). ret(x) store(x) pops a value from the operand stack and stores it in the x-th local variable. ret(x) is an instruction for jumping to the address stored in the x-th local variable. Note that, in contrast to JVM, our virtual machine has only one instruction for the store operation. Subroutines in JVM have increased the diculty of bytecode verication by the following reasons. { The return address of ret(x) can only be determined after values of local variables are analyzed by the bytecode verier. On the other hand, return addresses aect control ow and analysis of local variables. { In some situations, a subroutine does not return to the immediate caller but returns to an outer caller, such as the caller of the caller. { Inside a subroutine, local variables may have values of dierent types depending on a caller of the subroutine. In this paper, to cope with the last problem, we introduce types of the form last(x). For a value, to have this type means have to the same value as that of the x-th local variable in the caller of a subroutine. As an example, let us consider the following program. const0 store(1) 2 : jsr(7) constnull store(1) 5 : jsr(7) halt

7 : store(0) load(1) store(2) ret(0) Subroutine 7 is called from two callers (2 and 5). The return address is stored in variable 0 (the 0-th local variable). The value of variable 1 is an integer when the subroutine is called from caller 2, and is an object pointer when called from caller 5. This is a typical case in which a subroutine is polymorphic. In this program, the value of variable 1 is copied to variable 2. As JVM has dierent store instructions depending on the type of the operand, the above program is impossible under JVM. However, even if JVM allows the untyped store instruction, the bytecode verier of JVM signals an error for the above program, because it always assigns a unique type for a variable accessed in a subroutine. According to the specication of JVM [8], { Each instruction and each jsr targets needed to reach that instruction, a bit vector is maintained of all local variables accessed or modied since the execution of the jsr instruction. { For any local variable for which the bit vector (constructed above) indicates that the subroutine has accessed or modied, use the type of the local variable at the time of the ret. { For other local variables, use the type of the local variable before the jsr instruction. The work of Stata and Abadi as well as that of Qian faithfully follows this specication. For each subroutine, it is recorded which local variables are accessed or modied in it. Those variables that are not accessed or modied are simply ignored during analysis of the subroutine. By the method of this paper, to variable 1 in subroutine 7 is assigned a type of the form last(1). This means that a value in it is passed from variable 1 of the caller. This type is propagated through instructions in the subroutine. In particular, by the instruction store(2), the type of variable 2 becomes last(1). This information is used when the control returns from the subroutine to the caller. In this way, polymorphism of local variables in a subroutine is expressed by types of the form last(x). In our method, return addresses have types of the form return(n). A type of the form return(n) means to return to the n-th outer caller. For example, the address returning to the immediate caller has type return(1), while the address returning to the caller of the caller has type return(2). In Stata and Abadi's work (and similarly in Qian's work), return addresses have types of the form (ret-from L), where L is the address of the subroutine, from which the callers of the subroutines are obtained. In our analysis, the callers are obtained from the set of histories assigned to each instruction (cf. Section 4.3).

3 Virtual Machine 3.1 Values A value is a return address or an integer or an object pointer. We can easily add other kinds of values, such as that of oating point number. In the following formal treatment of the operational semantics of the virtual machine, a return address has the constructor retaddr, an integer the constructor intval, and an object pointer the constructor objval. They all take an integer as an argument. 3.2 Instructions A bytecode program is a list of instructions. An instruction takes one of the following formats. jsr(l) (L: subroutine address) ret(x) (x: variable index) load(x) (x: variable index) store(x) (x: variable index) const0 constnull inc(x) (x: variable index) if0(l) (L: branch address) ifnull(l) (L: branch address) halt Each mnemonic is considered as a constructor of instructions. Some of the mnemonics takes a nonnegative integer x or L as an operand. 3.3 Operational Semantics The virtual machine consists of { the program, which is a list of instructions and denoted by P, { the program counter, which is an index to P, { the local variables, where the list of values of the local variables is denoted by f, and { the operand stack, denoted by s. Let us use the notation l[i] for extracting the i-th element of list l, where the rst element of l has the index 0. The i-th instruction of the program P is denoted by P [i]. The value of the x-th local variable is denoted by f[x]. The p-th element of the operand stack s is denoted by s[p], where s[0] denotes the top element of s. As in the work by Stata and Abadi, the operational semantics of the virtual machine is dened as a transition relation between triples of the form hi; f; si, where i is the program counter, i.e., the index to the program P, f the value

list of the local variables, and s the operand stack. While the length of s may change during execution of the virtual machine, the length of f, i.e., the number of local variables is unchanged. The program P, of course, never changes during execution. The transition relation is dened as follows. { If P [i] = jsr(l), then hi; f; si! hl; f; retaddr(i + 1)::si: The return address retaddr(i + 1) is pushed onto the operand stack. The operator :: is the cons operator for lists. { If P [i] = ret(x) and f[x] = retaddr(j + 1), then { If P [i] = load(x), then { If P [i] = store(x), then hi; f; si! hj + 1; f; si: hi; f; si! hi + 1; f; f[x]::si: hi; f; v::si! hi + 1; f[x 7! v]; si: The notation f[x 7! v] means a list whose element is the same as that of f except for the x-th element, which is set to v. { If P [i] = const0, then { If P [i] = constnull, then hi; f; si! hi + 1; intval(0)::si: hi; f; si! hi + 1; objval(0)::si: { If P [i] = inc(x) and f[x] = intval(k), then { If P [i] = if0(l), then hi; f; si! hi + 1; f[x 7! intval(k + 1)]; si: If P [i] = if0(l) and k 6= 0, then { If P [i] = ifnull(l), then If P [i] = ifnull(l) and k 6= 0, then hi; f; intval(0)::si! hl; f; si: hi; f; intval(k)::si! hi + 1; f; si: hi; f; objval(0)::si! hl; f; si: hi; f; objval(k)::si! hi + 1; f; si:

The transition relation! is considered as the least relation satisfying the above conditions. The relation is dened so that when a type error occurs, no transition is dened. This means that to show type safety is to show that a transition sequence stops only at the halt instruction. For proving the correctness of our bytecode analysis, we also need another version of the operational semantics that maintains invocation histories of subroutines. This semantics corresponds to the structured dynamic semantics of Stata and Abadi. The transition relation is now dened for quadruples of the form hi; f; s; hi, where the last component h is an invocation history of subroutines. It is a list of addresses of callers of subroutines. This component is only changed by the jsr and ret instructions. { If P [i] = jsr(l), then hi; f; s; hi! hl; f; retaddr(i + 1)::s; i::hi: Note that the address i of the caller of the subroutine is pushed onto the invocation history. { If P [i] = ret(x), f[x] = retaddr(j + 1) and h = h 0 @[j]@h 00, where j does not appear in h 0, then hi; f; s; hi! hj + 1; f; s; h 00 i: The operator @ is the append operator for lists. For other instructions, the invocation histories before and after transition are the same. As for the two transition relations, we immediately have the following proposition. Proposition 1: If hi; f; s; hi! hi 0 ; f 0 ; s 0 ; h 0 i, then hi; f; si! hi 0 ; f 0 ; s 0 i. 4 Analysis 4.1 Types Types in our analysis are among the following syntactic entities: >;? (top and bottom) INT; OBJ; (basic types) return(n) (n: caller level) last(x) (x: variable index) A type is >,?, a basic type, a return type, or a last type. In this paper, we assume as basic types INT, the type of integers, and OBJ, the type of object pointers. It is easy to add other basic types, such as that of oating point numbers.

return types and last types are only meaningful inside a subroutine. A return type is the type of a return address. For positive integer n, return(n) denotes the type of the address for returning to the n-th outer caller. For example, return(1) denotes the type of the address for returning to the direct caller of the subroutine, and return(2) the type of the address for returning to the caller of the caller. A last type means that a value is passed from the caller of the subroutine. For nonnegative integer x, last(x) denotes the type of a value that was stored in the x-th local variable of the caller. A value can have this type only when it is exactly the same as the value of the x-th local variable when the subroutine was called. 4.2 Order among Types We dene the order among types as follows. > > INT >? > > OBJ >? > > return(n) >? > > last(x) >? Since we do not distinguish object pointers by their classes in this paper, the order is at, with > and? as the top and bottom elements. This order is extended to lists of types. For type lists t 1 and t 2, t 1 > t 2 holds if and only if t 1 and t 2 are of the same length and t 1 [i] > t 2 [i] holds for any i ranging over the indices for the lists. 4.3 Target of Analysis The target of our bytecode analysis is to obtain the following pieces of information for the i-th instruction of the given program P. F i S i H i F i is a type list. F i [x] describes the type of f[x], i.e., the value of the x-th local variable of the virtual machine. S i is a also type list. Each element of S i describes the type of the corresponding element of the operand stack of the virtual machine. Both F i and S i describe the types of the components of the virtual machine just before the i-th instruction is executed. H i is a set of invocation histories for the i-th instruction. F, S and H should follow a rule that is dened for each kind of P [i]. The rule says that certain conditions must be satised before and after the execution of P [i]. Rule for jsr) If h 2 H i and P [i] = jsr(l), then the following conditions must be satised. { 0 L < jp j.

{ For each variable index y, either F L [y] return(n + 1) (if F i [y] = return(n)), and F L [y] F i [y] (if F i [y] is neither return nor last) or F L [y] last(y) (even if F i [y] is last). { js L j = js i j + 1, where jlj denotes the length of list l. { S L [0] return(1). { For each index p, where 0 p < js i j, S i [p] is not last, S L [p + 1] S i [p] (if S i [p] is not return), and S L [p + 1] return(n + 1) (if S i [p] = return(n)). { i does not appear in h. (Recursion is not allowed.) { i::h 2 H L. Note that when F i [y] is not last, F L [y] cannot be determined uniquely. We must use some kind of backtracking for implementing our analysis. The following gures show the two possibilities for typing local variables inside a subroutine. In this example, it is assumed that there is only one caller (2) of subroutine 7. The column [F i [0]; F i [1]; F i [2]] shows the types of local variables (before each instruction). i instruction [F i [0]; F i [1]; F i [2]] S i H i 0 const0 [>; >; >] [] f[]g 1 store(1) [>; >; >] [INT] f[]g 2 jsr(7) [>; INT; >] [] f[]g 3 constnull [>; INT; INT] [] f[]g 7 store(0) [>; INT; >] [r(1)] f[2]g 8 load(1) [r(1); INT; >] [] f[2]g 9 store(2) [r(1); INT; >] [INT] f[2]g 10 ret(0) [r(1); INT; INT] [] f[2]g i instruction [F i [0]; F i [1]; F i [2]] S i H i 0 const0 [>; >; >] [] f[]g 1 store(1) [>; >; >] [INT] f[]g 2 jsr(7) [>; INT; >] [] f[]g 3 constnull [>; INT; INT] [] f[]g 7 store(0) [l(0); l(1); l(2)] [r(1)] f[2]g 8 load(1) [r(1); l(1); l(2)] [] f[2]g 9 store(2) [r(1); l(1); l(2)] [l(1)] f[2]g 10 ret(0) [r(1); l(1); l(1)] [] f[2]g

Rule for ret) If h 2 H i and P [i] = ret(x), then the following conditions must be satised. { F i [x] = return(n). { h = h 0 @[j]@h 00, where jh 0 j = n? 1. { 0 j + 1 < jp j. { For each variable index y, F j+1 [y] follow last(n; h; F i [y]). { S j+1 follow last(n; h; S i ). { h 00 2 H j+1. follow last is a function for extracting the type of a variable in a caller of a subroutine according to an invocation history. For nonnegative integer n, invocation history h and type t, follow last(n; h; t) is dened as follows. follow last(0; h; t) = t follow last(n + 1; i::h; return(m)) = if m > n + 1 then return(m? n? 1) else > follow last(n + 1; i::h; last(x)) = follow last(n; h; F i [x]) follow last(n + 1; i::h; t) = t (otherwise) follow last is extended to type lists, i.e., follow last(n; h; t) is also dened when t is a type list. Rule for load) If h 2 H i and P [i] = load(x), then the following conditions must be satised. { 0 i + 1 < jp j. { F i+1 F i. { S i+1 F i [x]::s i. { h 2 H i+1. Rule for store) If h 2 H i and P [i] = store(x), then the following conditions must be satised. { 0 i + 1 < jp j. { S i = t::t. { F i+1 F i [x 7! t]. { S i+1 t. { h 2 H i+1. Rule for const0) must be satised. { 0 i + 1 < jp j. { F i+1 F i. { S i+1 INT ::S i. { h 2 H i+1. If h 2 H i and P [i] = const0, then the following conditions

The rule for constnull is similar. Rule for inc) If h 2 H i and P [i] = inc(x), then the following conditions must be satised. { 0 i + 1 < jp j. { F i [x] = INT. { F i+1 F i. { S i+1 S i. { h 2 H i+1. Rule for if0) If h 2 H i and P [i] = if0(l), then the following conditions must be satised. { 0 L < jp j. 0 i + 1 < jp j. { S i = INT ::t. { F L F i. F i+1 F i. { S L t. S i+1 t. { h 2 H L. h 2 H i+1. The rule for ifnull is similar. Rule for halt) There is no rule for halt. 4.4 Correctness of Analysis In order to state the correctness of our analysis, we rst introduce the following relation. hv; hi : t v is a value and h is an invocation history. t is a type. By hv; hi : t, we mean that the value v belongs to the type t provided that v appears with the invocation history h. Following is the denition of this relation. { hv; hi : >. { hintval(k); hi : INT. { hobjval(k); hi : OBJ. { If h[n? 1] = j, then hretaddr(j + 1); hi : return(n). { If hv; hi : F i [x], then hv; i::hi : last(x). This denition is also inductive, i.e., hv; hi : t holds if and only if it can be derived only by the above rules. We have two lemmas. Lemma 1: If hv; hi : t and t 0 t, then hv; hi : t 0. Lemma 2: Let h 0 be a prex of h of length n and h 00 be its corresponding sux, i.e., h = h 0 @h 00 and jh 0 j = n. If hv; hi : t, then hv; h 00 i : follow last(n; h; t). We say that the quadruple hi; f; s; hi is sound with respect to hf; S; Hi and write hi; f; s; hi : hf; S; Hi, if the following conditions are satised.

{ 0 i < jp j. { For each variable index y, hf[y]; hi : F i [y]. { For each index p for s, hs[p]; hi : S i [p]. { h 2 H i. { h does not have duplication, i.e., no element of h occurs more than once in h. We have the following correctness theorem. It says that if F, S and H follow the rule for each instruction of P, then the soundness is preserved under the transition of quadruples. This means that if the initial quadruple is sound, then quadruples that appear during execution of the virtual machine are always sound. Theorem (correctness of analysis): Assume that F, S and H follow the rule for each instruction of P. If hi; f; s; hi : hf; S; Hi and hi; f; s; hi! hi 0 ; f 0 ; s 0 ; h 0 i, then hi 0 ; f 0 ; s 0 ; h 0 i : hf; S; Hi. The theorem is proved by the case analysis on the kind of P [i]. In this short paper, we only examine the case when P [i] = ret(n). Assume that hi; f; s; hi : hf; S; Hi and hi; f; s; hi! hi 0 ; f 0 ; s 0 ; h 0 i. Since F, S and H follow the rule for ret, the following facts hold. (i) F i [x] = return(n). (ii) h = h 1 @[j]@h 2, where jh 1 j = n? 1. (iii) 0 j + 1 < jp j. (iv) For each variable index y, F j+1 [y] follow last(n; h; F i [y]). (v) S j+1 follow last(n; h; S i ). (vi) h 2 2 H j+1. By (i) and the soundness of hi; f; s; hi, hf[x]; hi : return(n). Therefore, by (ii), f[x] = retaddr(j+1) and i 0 = j+1. Moreover, since h does not have duplication, h 1 does not contain j. This implies that h 0 = h 2. We also have that f 0 = f and s 0 = s. Let us check the conditions for the soundness of hi 0 ; f 0 ; s 0 ; h 0 i = hj+1; f; s; h 2 i. { By (iii), 0 i 0 < jp j. { By (iv), F i 0[y] follow last(n; h; F i [y]). By the soundness of hi; f; s; hi, hf[y]; hi : F i [y]. By Lemma 2, hf[y]; h 0 i : follow last(n; h; F i [y]). Therefore, by Lemma 1, hf[y]; h 0 i : F i 0[y]. { Similarly, by (v), we have that hs[p]; h 0 i : S i 0[p]. { By (vi) and since h 0 = h 2, h 0 2 H i 0. { Finally, since h does not have duplication, h 0 does not have duplication, either. Proposition 2: If hi; f; s; hi : hf; S; Hi and hi; f; si! hi 0 ; f 0 ; s 0 i, then there exists some h 0 such that hi; f; s; hi! hi 0 ; f 0 ; s 0 ; h 0 i.

The only case that must be examined is that of ret. Note that h 0 is uniquely determined. The above proposition guarantees that if F, S and H follow the rule for each instruction and the initial quadruple hi; f; s; hi is sound, then the transition sequence starting from the triple hi; f; si can always be lifted to a sequence starting from hi; f; s; hi. This means that the semantics for triples and that for quadruples coincide when F, S and H follow the rule for each instruction. The following nal theorem guarantees type safety. Theorem (type safety): If F, S and H follow the rule for each instruction and the initial quadruple hi; f; s; hi is sound, then a transition sequence stops only at the halt instruction. 4.5 Example Let us abbreviate last(x) and return(n) by l(x) and r(n), respectively. Following is the result of analyzing the example in Section 2. i instruction [F i [0]; F i [1]; F i [2]] S i H i 0 const0 [>; >; >] [] f[]g 1 store(1) [>; >; >] [INT] f[]g 2 jsr(7) [>; INT; >] [] f[]g 3 constnull [>; INT; INT] [] f[]g 4 store(1) [>; INT; INT] [OBJ] f[]g 5 jsr(7) [>; OBJ; INT] [] f[]g 6 halt [>; OBJ; OBJ] [] f[]g 7 store(0) [l(0); l(1); l(2)] [r(1)] f[2]; [5]g 8 load(1) [r(1); l(1); l(2)] [] f[2]; [5]g 9 store(2) [r(1); l(1); l(2)] [l(1)] f[2]; [5]g 10 ret(0) [r(1); l(1); l(1)] [] f[2]; [5]g The rule for ret(0) at 10 is satised because, for [2] 2 H 10, { F 10 [0] = return(1). { [2] = []@[2]@[]. { 0 2+1 = 3 < 11. { F 3 [0] = > follow last(1; [2]; F 10 [0]) = follow last(1; [2]; return(1)) = >. { F 3 [1] = INT follow last(1; [2]; F 10 [1]) = follow last(1; [2]; last(1)) = F 2 [1] = INT. { F 3 [2] = INT follow last(1; [2]; F 10 [2]) = follow last(1; [2]; last(1)) = F 2 [1] = INT. { S 3 = []. { [] 2 f[]g = H 3, and similarly for [5] 2 H 10.

4.6 Returning to an Outer Caller When P [i] = ret(x) returns to the caller of the caller, for example, F i [x] must be equal to the type return(2): In this case, H i should consist of histories of length at least 2. If [j 1 ; j 2 ; j 3 ; ] is in H i, P [i] returns to i 0 = j 2 +1 and F i 0 should satisfy F i 0[y] follow last(2; [j 1 ; j 2 ; j 3 ; ]; F i [y]): If F i [y] = last(y) and F j1 = last(y), for example, then follow last(2; [j 1 ; j 2 ; j 3 ; ]; F i [y]) = F j2 [y]: This is how information at j 2 (which is a jsr) is propagated to i 0 = j 2 +1. If F i [y] is not last, information at i is propagated. 5 Implementation A dataow analysis is usually implemented by an iterative algorithm. For each instruction, we check if the rule for the instruction is satised by F, S and H. If not, we update F, S and H accordingly, and check the next instruction that is aected by the update. There are two problems for implementing our analysis by such an iterative algorithm. Firstly, the rule for jsr does not uniquely determine F L [y] when F i [y] is not last. We have two choices: one is to set F L [y] = last(y), and the other is to set F L [y] = F i [y] (or F L [y] = return(n + 1) if F i [y] = return(n)). In our current implementation, we rst set F L [y] = last(y) and proceed the analysis. If the analysis fails at some point because F L [y] = last(y), we take the alternative and redo the analysis from L. (We need not completely abandon the work after we set F L [y] = last(y).) The second problem is that by a nave iterative algorithm, a subroutine is analyzed each time it is called. In the worst case this may require an exponential number of steps with respect to the length of the program. This problem can be avoided by representing invocation histories by a node in the call graph of the program, which is a graph whose nodes are addresses of subroutines and whose (directed) edges are labeled with addresses of jsr instructions. Since JVM does not allow recursion, it is a connected acyclic graph with a unique root node representing the initial address. A path from the root to a node in the graph corresponds to an invocation history by concatenating the labels of edges in the path. Each node in the graph then represents the set of all the invocation histories from the root to the node. Now, instead of keeping a set of invocation histories (i.e., H i ), we can keep a set of nodes in the graph. From a program in the following (left), the call graph in the right is constructed. The node L 3 represents the set f[c; b; a]; [c; b 0 ; a 0 ]; [c 0 ; b; a]; [c 0 ; b 0 ; a 0 ]g of invocation histories.

a : jsr(l 1 ) : : : a 0 : jsr(l 0 1) : : : L 1 : : : : b : jsr(l 2 ) : : : L 0 1 : : : : b 0 : jsr(l 2 ) : : : L 2 : : : : c : jsr(l 3 ) : : : c 0 : jsr(l 3 ) : : : L 3 : : : : L 3 c H H HY H c 0 L 2? b??? @I @ b 0 @ @ L 1 L 0 1 @I @ a @ @??? a 0? If histories are represented by nodes in the call graph, then the values that H i can take are bounded by the set of all the nodes in the call graph. This means that H i can only be updated for the number of times equal to the number of nodes. 6 Concluding Remark Since we introduced types of the form last(x), it has become possible to assign types to polymorphic subroutines that move a value from a variable to another. Our analysis is towards real polymorphism of subroutines in binary code, because we do not simply ignore unaccessed variables. For some programs, our method is more powerful than existing ones. In particular, we impose no restrictions on the number of entries and exits of a subroutine. It is also important that the correctness proof of bytecode verication becomes extremely simple. A dataow analysis, in general, assigns an abstract value x i to the i-th instruction so that a certain predicate P (x i ; ) always holds for any state that reaches the i-th instruction. To this end, for any transition! 0, where 0 is at i 0, one must show that P (x i ; ) implies P (x i 0 ; 0 ). Since corresponds to hi; f; s; hi in our analysis, x i seems to correspond to hf i ; S i ; H i i. However, the predicate hi; f; s; hi : hf; S; Hi, which should correspond to P (x i ; ), does not only refer to hf i ; S i ; H i i. When F i [y] is last, it also refers to F h[0]. This means that in terms of last types, our analysis relates values assigned to dierent instructions. This makes the analysis powerful while keeping the overall data structure for the analysis compact. The representation of invocation histories by a node in the call graph is also for making the data structure small and ecient. By this representation, the

number of updates of H i is limited by the size of the call graph, and an iterative algorithm is expected to stop in polynomial time with respect to the program size. This kind of complexity analysis should be made more rigorously in the future. Acknowledgments The author would like to thank Zhenyu Qian and Martn Abadi for their comments on the earlier draft of this paper. References 1. Richard M. Cohen: The Defensive Java Virtual Machine Specication, Version Alpha 1 Release, DRAFT VERSION, 1997. http://www.cli.com/software/djvm/html-0.5/djvm-report.html 2. Drew Dean: The Security of Static Typing with Dynamic Linking, Fourth ACM Conference on Computer and Communication Security, 1997, pp.18{27. http://www.cs.princeton.edu/sip/pub/ccs4.html 3. Sophia Drossopoulou and Susan Eisenbach: Java is Type Safe Probably, ECOOP'97 Object-Oriented Programming, Lecture Notes in Computer Science, Vol.1241, 1997, pp.389{418. http://outoften.doc.ic.ac.uk/projects/slurp/papers.html#ecoop 4. Sophia Drossopoulou, Susan Eisenbach and Sarfraz Khurshid: Is the Java Type System Sound? Proceedings of the Fourth International Workshop on Foundations of Object-Oriented Languages, 1997. http://outoften.doc.ic.ac.uk/projects/slurp/papers.html#tapos 5. Allen Goldberg: A Specication of Java Loading and Bytecode Verication, 1997. http://www.kestrel.edu/~goldberg/ 6. James Gosling, Bill Joy and Guy Steele: The Java TM Language Specication, Addison-Weslay, 1996. 7. Kimera: http://kimera.cs.washington.edu/ 8. Tim Lindholm and Frank Yellin: The Java TM Virtual Machine Specication, Addison-Weslay, 1997. 9. Gary McGraw and Edward W. Felten: Java Security: Hostile Applets, Holes and Antidotes, John Wiley and Sons, 1996. 10. George C. Necula: Proof-Carrying Code, the Proceedings of the 24th Annual SIGPLAN-SIGACT Symposium on Principles of Programming Languages, 1997, pp.106{117. 11. George C. Necula, Peter Lee: The Design and Implementation of a Certifying Compiler, submitted to PLDI'98. 12. Tobias Nipkow and David von Oheimb: Java light is Type-Safe Denitely, Proceedings of the 25th Annual SIGPLAN-SIGACT Symposium on Principles of Programming Languages, 1998, pp.161{170. 13. Zhenyu Qian: A Formal Specication of Java TM Virtual Machine Instructions, 1997. http://www.informatik.uni-bremen.de/~qian/abs-fsjvm.html 14. Vijay Saraswat: Java is not type-safe, 1997. http://www.research.att.com/~vj/bug.html

15. Secure Internet Programming: http://www.cs.princeton/edu/sip/ 16. Raymie Stata and Martn Abadi: A Type System for Java Bytecode Subroutines, Proceedings of the 25th Annual SIGPLAN-SIGACT Symposium on Principles of Programming Languages, 1998, pp.149{160. 17. Don Syme: Proving Java Type Soundness, 1997. http://www.cl.cam.ac.uk/users/drs1004/java.ps 18. David Wragg, Sophia Drossopoulou and Susan Eisenbach: Java Binary Compatibility is Almost Correct, Version 2, 1998. http://outoften.doc.ic.ac.uk/projects/slurp/papers.html#bincomp