99
Implementation of Virtual Machines Introduction

Implementation of Virtual Machines - · PDF fileImplementation of Virtual Machines ... Toy Bytecode File Format ... buggy compiler, or no compiler at all malicious intent

Embed Size (px)

Citation preview

Implementation ofVirtual Machines

Introduction

Usual Programming LanguageImplementation

Another Programming LanguageImplementation

And Another Implementation

An OverviewSource code is translated into an intermediate representation, (IR)The IR can be processed in these different ways:1. compile-time (static) translation to machine code2. emulation of the IR using an interpreter3. run-time (dynamic) translation to machine code = JIT (Just-In-

Time) compilingWhat is IR?

IR is code for an idealized computer, a virtual machine.

A Virtual Machine

= a computer which is emulated by a program.NOTES:

There is no reason why the virtual machinecannot have the architecture of a real computer.The emulation program can run on a real computer with the same architecture as the virtual machine being emulated.

Examples:Language IR Implementation(s)Java JVM bytecode Interpreter, JITC# MSIL JIT (but may be pre- compiled)Prolog WAM code compiled, interpretedForth bytecode interpretedSmalltalk bytecode interpretedPascal p- code interpreted

-- compiledC, C++ -- compiled (usually)Perl 6 PVM interpreted

Parrot interpreted, JITPython -- interpretedsh, bash, cshoriginal text interpreted

A Toy Intermediate RepresentationOpcode MeaningLDI k Push a four byte integer constant k onto stackLD n Fetch variable number n, push onto stackST n pop value off stack, store into variable nADD pop 2 values off stack, add them, push result on stackSUB similarly, except subtract themEQ pop 2 values off stack, push 1 if equal, push 0 otherwiseNE similarly, except compare value for not-equal propertyGT similarly, except compare values for greater than propertyJMP n jump to instruction at byte position nJMPF n pop value off stack, jump to position n if value is zeroREAD read one integer value from console, push onto stackWRITE pop value off stack and print it in console windowSTOP stop execution

A Translation from Toy Source toToy Assembler

int a, b;read(a);read(b);while(a != b) {if (a > b)

a = a - b;else

b = b - a;}write(a);

0 READ1 ST 02 READ3 ST 14 L3: LD 05 LD 16 NE7 JMPF L18 LD 09 LD 110 GT11 JMPF L2

12 LD 013 LD 114 SUB15 ST 016 JMP L417 L2: LD 118 LD 019 SUB20 ST 121 L4: JMP L322 L1: LD 023 WRITE24 STOP

Toy Bytecode File FormatWe need a representation scheme for the bytecode. A simple one is:

to use one byte for an opcode,four bytes for the operand of LDI,two bytes for the operands of LD, ST, JMP and JMPF.

As well as 0 for STOP, we will use this opcodenumbering:LDI LD ST ADD SUB EQ NE GT JMP JMPF READ WRITE1 2 3 4 5 6 7 8 9 10 11 12

The order of the bytes in the integer operandsis important. We will use big-endian order.

The Classic Interpreter ApproachIt emulates the fetch/decode/execute stages of a computer.

for( ; ; ) {opcode = code[pc++];switch(opcode) {case LDI:

val = fetch4(pc); pc += 4;push(val);break;

case LD:num = fetch2(pc); pc += 2;push( variable[num] );break;

...case SUB:

right = pop(); left = pop();push( right-left );

...

case JMP:pc = fetch2(pc);break;

case JMPF:val = pop();if (val)

pc += 2;else

pc = fetch2(pc);break;

...} /* end of switch */

} /* end of for loop */

The Classic InterpreterApproach, cont’d

DISPATCH:opcode = code[pc++];goto *OpcodeTable[opcode];

LDI:val = fetch4(pc); pc += 4;push(val);goto DISPATCH;

LD:num = fetch2(pc); pc += 2;push( variable[num] );goto DISPATCH;

...

A switch statement performs unnecessary work. In assembly language and gcc C, we can implement the interpreter loop as:

Critique

The classic interpreter is easy to implement.It is flexible – it can be extended to support tracing, profiling, checking for uninitialized variables, debugging, ... anything.The size of the interpreter plus the bytecode is normally much less than the equivalent compiled program.But interpretive execution is slow when compared to a compiled program.

The slowdown is 1 to 3 orders of magnitude (depending on the language).What can we do to speed up our interpreter?

Improving the Classic Interpreter

Verification – verify that all opcodes and all operands are valid before beginning execution, thus avoiding run- time checks. We should also be able to verify that stacks cannot overflow or underflow.Avoid unaligned data.

We can eliminate one memory access per IR instruction by expanding opcode numbers to addresses of the opcode implementations ...

LD1

Classic Interpreter withOperation Addresses

The bytecode file ... as in our exampleREAD; ST 0; READ; ST 1; LD 0; LD 1; NE; JMPF 54; LD 0; LD 1; GT; JMPF 41; LD 0; LD 1; SUB; ST 0; JMP 51; LD 1; LD 0; SUB; ST 1; JMP 8; LD 0; WRITE; STOPwould be expanded into the following values whenloaded into the interpreter’s bytecode array.Each value is a 4 byte address or a 4- byte operand.

Classic Interpreter, cont’dNow the interpreter dispatch loop becomes:

The C code can be a bit better still ...

pc = 0; /* index of first instruction */DISPATCH:

goto *code[pc++];LDI:

val = *code[pc++];push(val);goto DISPATCH;

LD:num = *code[pc++];push( variable[num] );goto DISPATCH;

...

Classic Interpreter, cont’dRecommended C style for accessing arrays is to use a pointer to the array elements, so we get:

But let’s step back and see a new technique –

pc = &code[0]; /* pointer to first instruction */DISPATCH:

goto *pc++;LDI:

val = *pc++;push(val);goto DISPATCH;

LD:num = *pc++;push( variable[num] );goto DISPATCH;

...

(Direct) Threaded Code Interpreters

Reference: James R. Bell, Communications of ACM 1973

Threaded Code Interpreters, cont’d

As before the bytecode is a sequence of addresses(intermixed with operands needed by the ops) ...&LDI | 99 | &LDI | 23 | &ADD | &ST | 5 ...The interpreter code looks like this ...

/* start it going */pc = &code[0];goto *code[pc++];LDI:

operand = (int)*pc++;push(operand);goto *code[pc++];

ADD:right = pop();left = pop();push(left+right);goto *code[pc++];

Threaded Code Interpreters, cont’d

As before, better C style is to use a pointer to the next element in the code ...

This makes the implementation very similar to Bell’s, who programmed for the DEC PDP11.

/* start it going */pc = &code[0];goto *(*pc++);LDI:

operand = (int)(*pc++);push(operand);goto *(*pc++);

ADD:right = pop();left = pop();push(left+right);goto *(*pc++);

...

Critique of Threaded Code Interpreters

Much more efficient than the classic approachMore memory is needed (addresses are larger than opcodes, operands should be expanded to words to keep alignment)Less flexible ...

The classic approach with a goto table allows the possibility ofhot-swapping a different implementation of an opcode just by overwriting an address in the table;It’s not possible with threaded code.

If we want to recover that possibility, there is the IndirectThreaded Code approach.

Indirect Threaded Code Interpreters

Due to Robert Dewar, Communications of ACM 1974.For example,

LDI 10, LDI 23, ADDwould be implemented like this

Subroutine ImplementationThe bytecode file ... as in our exampleREAD; ST 0; READ; ST 1; LD 0; LD 1; NE; JMPF 54; LD 0; LD 1; GT;JMPF 41; LD 0; LD 1; SUB; ST 0; JMP 51; LD 1; LD 0; SUB; ST 1; JMP 8; LD 0; WRITE; STOPcan be translated, as the file is read, into machine code ... a mixtureof CALL instructions and data values (the operands).

etc.Each function is programmed in assembly language.(This is approach is getting close to JIT compilation.)

Further Improvements toInterpreters ...

A problem still being researched. (See the papers in the IVMEannual workshop.)Speed improvement ideas include:

Superoperators (see Proebsting, POPL 1995)Stack caching (see Ertl, PLDI 1995)Inlining (see Piumarta & Riccardi, PLDI 1998)Branch prediction (see Ertl & Gregg, PLDI 2003)

Space improvement ideas (for embedded systems?) include:

Huffman compressed code (see Latendresse & Feeley, IVME 2003)Superoperators – if used carefully (ibid)

The Java Virtualmachine

A Main Reference Source

The JavaTM Virtual Machine Specification (2nd Ed) by Tim Lindholm & Frank YellinAddison-Wesley, 1999The book is on-line and available for download:

http://java.sun.com/docs/books/vmspec/

Usage Pictures

The Java Classfile

Inspecting a Java Classfile

Given a file Foo.class, the commandjavap -c Foo will show the bytecode for all methods in class Foo.(Adding the –verbose option tells more about the methods.)

An example for gcd function:

class gcd {int gcd( int a, int b ) {

while( a != b ) {if (a > b)

a = a - b;else

b = b - a;}

return a;}

}

Output from javap command:% javap -c -verbose gcdCompiled from gcd.javaclass gcd extends java.lang.Object {

gcd(); /* Stack=1, Locals=1, Args_size=1 */int gcd(int, int); /* Stack=2, Locals=3, Args_size=3 */}

Method gcd()0 aload_01 invokespecial #1 <Method java.lang.Object()>4 return

Method int gcd(int, int)0 goto 193 iload_14 iload_25 if_icmple 158 iload_19 iload_210 isub11 istore_112 goto 1915 iload_216 iload_117 isub18 istore_219 iload_120 iload_221 if_icmpne 324 iload_125 ireturn

/* the code again */class gcd {

int gcd( int a, int b ) {while( a != b ) {if (a > b)

a = a - b;else

b = b - a;}

return a;}

}

JVM Runtime Behaviour

VM startupClass Loading/Linking/InitializationInstance Creation/FinalisationUnloading ClassesVM exit

VM Startup and Exit

StartupLoad, link, initialize class containing main()Invoke main() passing it the command-linearguments

Exit when:all non-daemon threads end, or

some thread explicitly calls the exit() method

Class LoadingFind the binary code for a class and create a correspondingClass objectDone by a class loader – bootstrap, or create your ownOptimize: prefetching, group loading, cachingEach class-loader maintains its own namespaceErrors include: ClassFormatError, UnsupportedClassVersionError, ClassCircularityError, NoClassDefFoundError

Class Linking - 1. Verification

Extensive checks that the .classfile is validThis is a vital part of the JVM security modelNeeded because of possibility of:

buggy compiler, or no compiler at allmalicious intent(class) version skew

Checks are independent of compiler and language

Class Linking - 2. Preparation

Create static fields for a classSet these fields to the standard default values (N.B. not explicit initializers)Construct method tables for a class... and anything else that might improveefficiency

Class Linking - 3. Resolution

Most classes refer to methods/fields from other classesResolution translates these names intoexplicit referencesAlso checks for field/method existence and whether access is allowed

Class Initialization

Happens once just before first instancecreation, or first useof static variable.Initialise the superclass first!Execute (class) static initializer codeExecute explicit initializers for static variablesMay not need to happen for use of final staticvariableCompleted before anything else sees this class

Instance Creation/FinalisationInstances are created using new, or newInstance() from classClassInstances of String may be created (implicitly) for String literalsProcess:1. Allocate space for all the instance variables (including the

inherited ones),2. Initialize them with the default values3. Call the appropriate constructor (do parent's first)_ finalize() is called just before garbage collector takesthe object (so timing is unpredictable)

The Method Area

Contains one entry for each classLists all details relating to that classIncludes the constant poolContains the code for the methodsMay grow/shrink as classes are loaded/unloadedShared by all threads.

The Heap

One entry for each objectIncreases with each instance creationDecreases with garbage collection (mechanism not specified)Object information: instance field values, pointer to class, locking info, virtual method table(?)Shared by all threads.

Java Stack

JVM pushes and pops frames onto this stackEach frame corresponds to the invocation of a methodCall a method → push its frame onto the stackReturn from a method → pop its frameFrame holds parameter values, local variables, intermediate values etc.

Stack Caching

IdeaThe idea is based on the observation that pushing results onto a stack in memory and popping them off when needed as operands is expensive – lots of memory accesses.If we keep the top k stack items in registers then1. most accesses would be much faster2. many updates to the stack pointer become unnecessary.Anton Ertl, Stack Caching for Interpreters Proceedingsof PLDI, 1995, pages 315- 327.

Picture (the stack grows up):

Example:Consider a bytecode sequence that corresponds to

i = (j + 2) * (k - 2);The byecode is similar to this (using symbolic names and constant values instead of indexes):

iload jldc 2iaddiload kldc 2isubimulistore i

Now, what happens when this is executed?

Example, cont’diload jldc 2iaddiload kldc 2isubimulistore i

Example, cont’diload jldc 2iaddiload kldc 2isubimulistore i

with top-of-stack in a register... the number of memory accesses is greatlyreduced

Example, cont’diload jldc 2iaddiload kldc 2isubimulistore i

with top two stack items in registers... the number of memory accesses is reduced further

How is Stack CachingImplemented?

A different implementation of each opcode is needed for each combination of operands in memory (on the stack) or in registers.Assuming two registers, then for iadd we have:1. both operands in memory,2. left operand in memory, right operand in register (2 cases?),3. both operands in registers (2 cases?).and the result would be kept in a register.(For #3, should we load the freed second register from memory to maintain two stack items in registers?)

For a generic CISC computer, we would havecode for the three versions of iadd something like this (and more cases where reg1 and reg2 are exchanged?):both operands in memory

load reg1,stack(sp)add reg1,stack+4(sp)sub sp,=8 ; adjust stack height

operand2 in memory, operand1 in reg1add reg1,stack(sp)sub sp,=4 ; adjust stack height

operand1 in reg1, operand2 in reg2add reg1,reg2

Implementation, cont’dWe can model the usage of registers as a FSA:

Implementation, cont’dThe FSA diagram shows 5 states and therefore potentially 5 implementations for each operation.We can analyze bytecode and choose the desiredimplementation (at compile time or bytecode preparation time):

The JamVM Approach to StackCaching

If USE_CACHE is true, it keeps the top two words in the variables named cache.i.v1 and cache.i.v2. (Words because a double or a longword = 2 words)

If there is one item cached, it goes in cache.i.v1If there are two items cached, the top one is cache.i.v2 and the top-but-one is cache.i.v1.There are three dispatch tables, handlers_0, handlers_1, and handlers_2, which are used when the cache contains 0, 1 and 2 words respectively.

Preparation determines the cache level of each operation, and then links the correct versions of operations together.

Java VirtualMachine, part two

Run-Time Data Areas

The Memory RegionsEach time a class is loaded, info about the class is copied into the method area.While a program is running, all instantiated objects (instances of classes, arrays) are allocated on the heap.When a new thread comes into existence, it getsits own Java stack and its own pc register.The pc register indicates which JVM instruction to execute next in a method’s bytecode.The Java stack contains all the state informationfor a thread.The state of native method invocations is held in a native method stack.

Data Areas Exclusive to a Thread

Datatypes of the JVM

Load and Store Instructions

Transferring values between local variablesand operand stack

iload, lload, fload, dload, aloadand special cases of the above: iload_0, iload_1 ...

istore, lstore, fstore, dstore, astorePushing constants onto the operand stack

bipush, sipush, ldc, ldc_w, ldc2_w, aconst_null, iconst_m1

and special cases: iconst_0, iconst_1, ...

Arithmetic Operations

Operands are normally taken from operandstack and the result pushed back there

iadd, ladd, fadd, daddisub ...imul ...idiv ...irem ...ineg ...iinc

Bitwise Operations

ior, loriand, landixor, lxorishl, lshlishr, iushr, lshr, lushr

Type Conversion Operations

Widening Operationsi2l, i2f, i2d, l2f, l2d, f2d

Narrowing Operationsi2b, i2c, i2s, l2i, f2i, f2l, d2i, d2l, d2f

Object Creation and manipulation

newnewarray, anewarray, multinewarraygetfield, putfield, getstatic, putstaticbaload, caload, saload, iaload, laload, faload, daload, aaloadbastore, castore, sastore, iastore, lastore, fastore, dastore, aastorearraylengthinstanceof, checkcast

Operand Stack Management

pop, pop2dup, dup2, dup_x1, dup_x2, dup2_x2, swap

Control Transferifeq, iflt, ifle, ifne, ifgt, ifgeifnull, ifnonnullif_icmpeq, if_icmplt, if_icmple, if_icmpne, if_icmpgt, if_icmpgeif_acmpeq, if_acmpnegoto, goto_w, jsr, jsr_w, ret

Switch statement implementationtableswitch, lookupswitch

Comparison operations for long, float & double typeslcmp, fcmpl, fcmpg, dcmpl, dcmpg

Method Invocation / Return

invokevirtualinvokespecialinvokeinterfaceinvokestaticireturn, freturn, dreturn, areturnreturn

Class Start-Up

Loading a Type

The following steps occur:obtain a stream of binary data that represents the type(usually by reading a classfile from disk)parse the binary data into internal data structures in the methodareacreate an instance of java.lang.Class that represents the new type

The java.lang.Class instance acts as an interface between the running program and the internal data structures.

VerificationEnsures that the type (i.e. the loaded class) obeys Java semantics, and will not violate the integrity of the JVM.There are many aspects to verificationSome Checks during Loading

if it’s a classfile, check the magic number (0xCAFEBABE),make sure that the file parses into its components correctly

Additional Checks after/during Loadingmake sure the class has a superclass (only Object does not)make sure the superclass is not finalmake sure final methods are not overriddenif a nonabstract class, make sure all methods are implementedmake sure there are no incompatible methodsmake sure constant pool entries are consistent

Additional Checks after/during Loading, cont’dcheck the format of special strings in the constant pool (such as method signatures etc)A Final Check (required before method isexecuted)verify the integrity of the method’s bytecodeThis last check is very complicated (so complicated that Sun got it wrong a few times)

Verifying BytecodeThe requirements

All the opcodes are valid, all operands (e.g. number of a field or a local variable) are in range.Every control transfer operation (goto, ifne, ...) must have a destination which is in range and is the start of an instructionType correctness: every operation receives operands with the correctdatatypesNo stack overflow or underflowA local variable can never be used before it has been initializedObject initialization – the constructor must be invoked before the class instance is usedExecution cannot fall off the end of the codeThe code does not end in the middle of an instructionFor each exception handler, the start and end points must be at the beginnings of instructions, and the start must be before the endException handler code must start at the beginning of an instruction

Sun’s Verification AlgorithmA before state is associated with each instruction.The state is:

contents of operand stack (stack height, and datatype of eachelement), pluscontents of local variables (for each variable, we record uninitialized or unusable or the datatype)

A datatype is integral, long, float, double or any reference typeEach instruction has an associated changed bit:

all these bits are false,except the first instruction whose changed bit is true.

Sun’s Verification Algorithm, cont’ddo forever {

find an instruction I whose changed bit is true;if no such instruction exists, return SUCCESS;set changed bit of I to false;state S = before state of I;for each operand on stack used by I

verify that the stack element in S has correct datatypeand pop the datatype from the stack in S;

for each local variable used by Iverify that the variable is initialized andhas the correct datatype in S;

if I pushes a result on the stack,verify that the stack in S does not overflow, andpush the datatype onto the stack in S;

if I modifies a local variable,record the datatype of the variable in S

... continued

Verification fails if a datatype does not match with what is required by the instruction, the stack underflows or overflows, or if twostates cannot be merged because the twostacks have different heights.

determine SUCC, the set of instructions which can follow I;(Note: this includes exception handlers for I)

for each instruction J in SUCC domerge next state of I with the before state of Jand set J’s changed bit if the before state changed;(Special case: if J is a destination because of an exceptionthen a special stack state containing a single instance ofthe exception object is created for merging with the beforestate of J.)

} // end of do forever

Sun’s Verification Algorithm, cont’d

Merging two statesTwo stack states with the same height are merged by pairwisemerging the types of corresponding elements.The states of the two sets of local variables are merged by mergingthe types of corresponding variables.

The result of merging two types:Two types which are identical merge to give the same typeFor two types which are not identical:if they are both references, then the result is the first commonsuperclass (lowest common ancestor in class hierarchy);otherwise the result is recorded as unusable.

Examplestatic int factorial( int n ) {

int res;for (res = 1; n > 0; n--) res = res * n;return res;

}Corresponding JVM bytecode:method static int factorial(int), 2 variables, 2 stack slots0: iconst_1 // push the integer constant 11: istore_1 // store it in variable 1 (res)2: iload_0 // push variable 0 (the n parameter)3: ifle 14 // if negative or null, go to PC 146: iload_1 // push variable 1 (res)7: iload_0 // push variable 0 (n)8: imul // multiply the two integers at top of stack9: istore_1 // pop result and store it in variable 110: iinc 0, -1 // decrement variable 0 (n) by 111: goto 2 // go to PC 214: iload_1 // load variable 1 (res)15: ireturn // return its value to caller

Some Additional JVM Instructionsinvokevirtual #m

#m refers to a method description in the constant pool; calls that method passing arguments on the stack; the first argument must be this; a result is returned on the stack.

invokespecial #mlike invokevirtual except used to call <init> methods or methods in superclass or private methods; the first argument is this.

invokestatic #mlike invokevirtual except calls a static method; there is no thisparameter.

new #c#c refers to an entry in the constant pool which must be a class name. A reference to an uninitialized instance of that class is pushed onto the operand stack.

getfield #f#f refers to an entry in the constant pool which defines a field of a class; the operand on the stack is a reference to the class.

putfield #ftakes two stack operands, a reference to a class instance and avalue to be stored in the field.

Abstract Interpretation Equations

Merging TypesThe lattice represents an ordering relation on typesThe lattice is derived from the semantics of Java (and isbased on the class hierarchy)Given any two types t1 and t2, there is a least upper bound type, lub(t1,t2)Given any type t, the length of the path from t to top, T, is finite (the well- foundedness property).The step in Sun’s verification algorithm where typesare merged is implemented as lub.The finiteness property guarantees that Sun’s algorithm will converge in a finite number of steps.

Given Java code like thisC foo;D d1 = new D(); // parent class of D is CE e1 = new E(); // parent class of E is Cif (sometest)

foo = d1;else

foo = e1;we get JVM bytecode for the if statement something like this:...91: ifeq 99 // jump if test is false94: load_2 // load d195: store_1 // store in foo96: goto 10199: load_3 // load e1100: store_1 // store in foo101: ... // variable 1 gets set to type C

Complications with Interface TypesJava has interface types:

interface I { ... }interface J { ... }class C1 implements I, J { ... }class C2 implements I, J { ... }

If we put the interfaces into the type lattice we get:

but now the lub relation does not yield a unique result.Leroy lists 3 solutions to the problem.

Complications with Interface Types, cont’dUse sets of types during

analysis:E.g., a JVM operation likeiconst_1 yields {I} as the typeof the topmost stack elementMerging two types is nowbased on set unionAdding extra types to force a lattice structure again

Complications with Interface Types, cont’dSun’s solution (according to Leroy):

During verification, interface types are considered to besynonyms for Object.The JVM instruction

invokeinterface I.mis checked at run-time to ensure that the object on top of the stack implements interface I and an exception is raised if it does not.

Complications with ObjectInitialization

Creation of an object and its initialization are separate actions in the JVM. An object cannot be used until it has been initialized bya call to a constructor.

The statementx = new C(arg);

is translated to code like this

new C // allocate uninitialized instance of Cdup // duplicate reference to instancecode to compute arginvokespecial C.<init> // call the initializerastore_3 // store reference to initialized object in x

How do we check that an initialized object is stored in x?

Complications with ObjectInitialization, cont’dTricky example:

x = new C( new C(null) );produces this JVM code (with stack states shown afterwards):

0: new C // C03: dup // C0,C04: new C // C0,C0,C47: dup // C0,C0,C4,C48: aconst_null // C0,C0,C4,C4,null9: invokespecial C.<init> // C0,C0,C412: invokespecial C.<init> // C015: astore_3 //

Complications with ObjectInitialization, cont’d

The subscript k tracks which instruction created the instance.When an instance with subscript k is initialized, all copies on the stack subscripted by k are assumed to be initialized too.This works only if the new instruction at position k is notexecuted again until the object created by an earlier execution of that new instruction has been initialized.Sun ensure correctness by forbidding backward jumps if the stack contains an uninitialized object. (This is a simple rule but there are less restrictive ways of guaranteeing correctness.)

Complications with SubroutinesCompilation of finally clauses normally usessubroutines:

Complications with Subroutines, cont’dThe ‘simple’ solution does not work:

The simple solution ismerging type information from jsr’s at 0 and 52 to 100,propagating type information from the ret at 110 back to both 3 and 55.It causes a verification failure for the iload at 55

Sun’s Approach for Handling Subroutines

The following is paraphrased from Section4.9.6 of the JVMspecification (2nd edition).

Each instruction keeps track of the list of jsr targetsneeded to reach that instruction. (For most code, the list is empty; for code in a finally clause, it has length 1; only for nested finally clauses can it be longer.)For each instruction and each jsr needed to reach thatinstruction, a bit vector is maintained of all localvariables accessed or modified since the execution of the jsr instruction.To make return points easier to analyze, twosubroutines cannot share the same ret instruction (a special Sun restriction).

Sun’s Approach for Handling Subroutines, cont’d

To analyze a ret instruction:find all jsr instructions that call the subroutine and merge the state of the operand stack and local variable array at the time of the ret instruction into the operand stack and local variable array of the instructions following the jsr.

Merging the types after a ret with the types after a jsr uses a special set of values for localvariables:

For any local variable that the bit vector (constructedabove) indicates has been accessed or modified bythe subroutine, use the type of the local variable at the time of the ret.For other local variables, use the type of the localvariable before the jsr instruction.

Another Verification Problem

Gal, Probst and Franz (Univ. of California at Irvine, Technical Report#03-23) show how to create bytecode where Sun’s verificationalgorithm requires O(n2) execution time.A verification time of 40 seconds can be achieved for a maximumsize method (65,535 bytes) running on a 2.5GHz P4 computer.20 such methods in one applet require 800 seconds or nearly 15 minutes for verification.Such applets can be used to mount a denial-of-service attack.