]> source.dussan.org Git - javassist.git/commitdiff
Fix subtypeOf in CtArray
authorjgreene <jgreene@30ef5769-5b8d-40dd-aea6-55b5d6557bb3>
Sat, 24 May 2008 05:13:20 +0000 (05:13 +0000)
committerjgreene <jgreene@30ef5769-5b8d-40dd-aea6-55b5d6557bb3>
Sat, 24 May 2008 05:13:20 +0000 (05:13 +0000)
Introduce full data-flow analysis API
Fix AALOAD by using data-flow analysis to determine the type
Introduce a testsuite to the project
Add a framedump toolp

git-svn-id: http://anonsvn.jboss.org/repos/javassist/trunk@437 30ef5769-5b8d-40dd-aea6-55b5d6557bb3

27 files changed:
.classpath
build.xml
lib/junit.jar [new file with mode: 0644]
src/main/javassist/CodeConverter.java
src/main/javassist/CtArray.java
src/main/javassist/bytecode/BadBytecode.java
src/main/javassist/bytecode/InstructionPrinter.java [new file with mode: 0644]
src/main/javassist/bytecode/analysis/Analyzer.java [new file with mode: 0644]
src/main/javassist/bytecode/analysis/Executor.java [new file with mode: 0644]
src/main/javassist/bytecode/analysis/Frame.java [new file with mode: 0644]
src/main/javassist/bytecode/analysis/FramePrinter.java [new file with mode: 0644]
src/main/javassist/bytecode/analysis/IntQueue.java [new file with mode: 0644]
src/main/javassist/bytecode/analysis/MultiArrayType.java [new file with mode: 0644]
src/main/javassist/bytecode/analysis/MultiType.java [new file with mode: 0644]
src/main/javassist/bytecode/analysis/Subroutine.java [new file with mode: 0644]
src/main/javassist/bytecode/analysis/SubroutineScanner.java [new file with mode: 0644]
src/main/javassist/bytecode/analysis/Type.java [new file with mode: 0644]
src/main/javassist/bytecode/analysis/Util.java [new file with mode: 0644]
src/main/javassist/bytecode/analysis/package.html [new file with mode: 0644]
src/main/javassist/convert/TransformAccessArrayField.java
src/main/javassist/convert/Transformer.java
src/main/javassist/tools/framedump.java [new file with mode: 0644]
src/test/test/Test.java [deleted file]
src/test/test/javassist/bytecode/analysis/AnalyzerTest.java [new file with mode: 0644]
src/test/test/javassist/bytecode/analysis/ErrorFinder.java [new file with mode: 0644]
src/test/test/javassist/bytecode/analysis/ScannerTest.java [new file with mode: 0644]
src/test/test/javassist/convert/ArrayAccessReplaceTest.java [new file with mode: 0644]

index deae2f478f71a3e4c05465613f8f8f028eedbeb2..2810d72786967e2888bf0baf325fa9257fb93a27 100644 (file)
@@ -3,5 +3,6 @@
        <classpathentry excluding="javassist/util/HotSwapper.java" kind="src" path="src/main"/>
        <classpathentry kind="src" path="src/test"/>
        <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-       <classpathentry kind="output" path="build/classes"/>
+       <classpathentry kind="lib" path="lib/junit.jar"/>
+       <classpathentry kind="output" path="eclipse-output/classes"/>
 </classpath>
index 2de5a2e88d1329273cd25539e7a31a93e879bc79..78e5c749f484414fe2ba589956158475673262b6 100644 (file)
--- a/build.xml
+++ b/build.xml
   <property environment="env"/>
   <property name="target.jar" value="javassist.jar"/>
   <property name="target-src.jar" value="javassist-src.jar"/>
+  <property name="lib.dir" value="${basedir}/lib"/>
   <property name="src.dir" value="${basedir}/src/main"/>
   <property name="build.dir" value="${basedir}/build"/>
   <property name="build.classes.dir" value="${build.dir}/classes"/>
+  <property name="test.src.dir" value="${basedir}/src/test"/>
+  <property name="test.build.dir" value="${basedir}/build/test-classes"/>
+  <property name="test.reports.dir" value = "${basedir}/build/test-output"/>
 
   <property name="run.dir" value="${build.classes.dir}"/>
 
 
   <property name="build.classpath" refid="classpath"/>
 
+  <path id="test.compile.classpath">
+    <pathelement location="${build.classes.dir}"/>
+    <pathelement location="${lib.dir}/junit.jar"/>
+  </path>
+
+  <property name="test.compile.classpath" refid="test.compile.classpath"/>
+
+  <path id="test.classpath">
+    <pathelement location="${test.build.dir}"/>
+    <pathelement location="${lib.dir}/junit.jar"/>
+    <pathelement location="${build.classes.dir}"/>
+  </path>
+
+  <property name="test.classpath" refid="test.classpath"/>
+
   <!-- =================================================================== -->
   <!-- Prepares the build directory                                        -->
   <!-- =================================================================== -->
   <target name="prepare" >
     <mkdir dir="${build.dir}"/>
     <mkdir dir="${build.classes.dir}"/>
+    <mkdir dir="${test.build.dir}"/>
+    <mkdir dir="${test.reports.dir}"/>
   </target>
 
   <!-- =================================================================== -->
     </javac>
   </target>
 
+  <target name="test-compile" depends="compile">
+    <javac srcdir="${test.src.dir}"
+           destdir="${test.build.dir}"
+           debug="on"
+           deprecation="on"
+           optimize="off"
+           includes="**">
+            <classpath refid="test.compile.classpath"/>
+    </javac>
+  </target>
+
+  <target name="test" depends="test-compile">
+    <junit fork="true" printsummary="true">
+      <classpath refid="test.classpath"/>
+      <formatter type="plain"/>
+      <formatter type="xml"/>
+      <batchtest todir="${test.reports.dir}">
+         <fileset dir="${test.build.dir}">
+           <include name="**/*Test.*"/>
+         </fileset>
+      </batchtest>
+    </junit>
+  </target>
+
   <target name="sample" depends="compile">
     <javac srcdir="${basedir}"
            destdir="${build.classes.dir}"
diff --git a/lib/junit.jar b/lib/junit.jar
new file mode 100644 (file)
index 0000000..674d71e
Binary files /dev/null and b/lib/junit.jar differ
index 74dcb8ffacae0addd361a84e04af71faba8b9f75..4ffe1eeb4880da4e0cd201dadd96af72b6c3a4ee 100644 (file)
@@ -470,9 +470,8 @@ public class CodeConverter {
         CodeAttribute codeAttr = minfo.getCodeAttribute();
         if (codeAttr == null || transformers == null)
             return;
-
         for (t = transformers; t != null; t = t.getNext())
-            t.initialize(cp, codeAttr);
+            t.initialize(cp, clazz, minfo);
 
         CodeIterator iterator = codeAttr.iterator();
         while (iterator.hasNext()) {
@@ -504,7 +503,7 @@ public class CodeConverter {
      * as array access replacements.
      *
      * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
-     * @version $Revision: 1.13 $
+     * @version $Revision: 1.14 $
      */
     public interface ArrayAccessReplacementMethodNames
     {
@@ -613,7 +612,7 @@ public class CodeConverter {
      * accesses to array elements.
      *
      * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
-     * @version $Revision: 1.13 $
+     * @version $Revision: 1.14 $
      */
     public static class DefaultArrayAccessReplacementMethodNames
         implements ArrayAccessReplacementMethodNames
index e46f24b548f685aca28f90433fa02e99d050cc17..559254839bafa306463090602f702aef7f76d252 100644 (file)
@@ -41,7 +41,8 @@ final class CtArray extends CtClass {
 
         String cname = clazz.getName();
         if (cname.equals(javaLangObject)
-            || cname.equals("java.lang.Cloneable"))
+            || cname.equals("java.lang.Cloneable")
+            || cname.equals("java.io.Serializable"))
             return true;
 
         return clazz.isArray()
index 4241fbd620ce2d113d70ae03baacf1e321b249b4..2f93b52d3a0005850d48e9360dfabd3c943480cd 100644 (file)
@@ -26,4 +26,8 @@ public class BadBytecode extends Exception {
     public BadBytecode(String msg) {
         super(msg);
     }
+
+    public BadBytecode(String msg, Throwable cause) {
+        super(msg, cause);
+    }
 }
diff --git a/src/main/javassist/bytecode/InstructionPrinter.java b/src/main/javassist/bytecode/InstructionPrinter.java
new file mode 100644 (file)
index 0000000..03d0390
--- /dev/null
@@ -0,0 +1,280 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License.  Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode;
+
+import java.io.PrintStream;
+
+import javassist.CtMethod;
+
+/**
+ * Simple utility class for printing the instructions of a method.
+ *
+ * @author Jason T. Greene
+ */
+public class InstructionPrinter implements Opcode {
+
+    private final static String opcodes[] = Mnemonic.OPCODE;
+    private final PrintStream stream;
+
+    public InstructionPrinter(PrintStream stream) {
+        this.stream = stream;
+    }
+
+    public static void print(CtMethod method, PrintStream stream) {
+        (new InstructionPrinter(stream)).print(method);
+    }
+
+    public void print(CtMethod method) {
+        MethodInfo info = method.getMethodInfo2();
+        ConstPool pool = info.getConstPool();
+        CodeAttribute code = info.getCodeAttribute();
+        if (code == null)
+            return;
+
+        CodeIterator iterator = code.iterator();
+        while (iterator.hasNext()) {
+            int pos;
+            try {
+                pos = iterator.next();
+            } catch (BadBytecode e) {
+                throw new RuntimeException(e);
+            }
+
+            stream.println(pos + ": " + instructionString(iterator, pos, pool));
+        }
+    }
+
+    public static String instructionString(CodeIterator iter, int pos, ConstPool pool) {
+        int opcode = iter.byteAt(pos);
+
+        if (opcode > opcodes.length || opcode < 0)
+            throw new IllegalArgumentException("Invalid opcode, opcode: " + opcode + " pos: "+ pos);
+
+        String opstring = opcodes[opcode];
+        switch (opcode) {
+            case BIPUSH:
+                return opstring + " " + iter.byteAt(pos + 1);
+            case SIPUSH:
+                return opstring + " " + iter.s16bitAt(pos + 1);
+            case LDC:
+                return opstring + " " + ldc(pool, iter.byteAt(pos + 1));
+            case LDC_W :
+            case LDC2_W :
+                return opstring + " " + ldc(pool, iter.u16bitAt(pos + 1));
+            case ILOAD:
+            case LLOAD:
+            case FLOAD:
+            case DLOAD:
+            case ALOAD:
+            case ISTORE:
+            case LSTORE:
+            case FSTORE:
+            case DSTORE:
+            case ASTORE:
+                return opstring + " " + iter.byteAt(pos + 1);
+            case IFEQ:
+            case IFGE:
+            case IFGT:
+            case IFLE:
+            case IFLT:
+            case IFNE:
+            case IFNONNULL:
+            case IFNULL:
+            case IF_ACMPEQ:
+            case IF_ACMPNE:
+            case IF_ICMPEQ:
+            case IF_ICMPGE:
+            case IF_ICMPGT:
+            case IF_ICMPLE:
+            case IF_ICMPLT:
+            case IF_ICMPNE:
+                return opstring + " " + (iter.s16bitAt(pos + 1) + pos);
+            case IINC:
+                return opstring + " " + iter.byteAt(pos + 1);
+            case GOTO:
+            case JSR:
+                return opstring + " " + (iter.s16bitAt(pos + 1) + pos);
+            case RET:
+                return opstring + " " + iter.byteAt(pos + 1);
+            case TABLESWITCH:
+                return tableSwitch(iter, pos);
+            case LOOKUPSWITCH:
+                return lookupSwitch(iter, pos);
+            case GETSTATIC:
+            case PUTSTATIC:
+            case GETFIELD:
+            case PUTFIELD:
+                return opstring + " " + fieldInfo(pool, iter.u16bitAt(pos + 1));
+            case INVOKEVIRTUAL:
+            case INVOKESPECIAL:
+            case INVOKESTATIC:
+                return opstring + " " + methodInfo(pool, iter.u16bitAt(pos + 1));
+            case INVOKEINTERFACE:
+                return opstring + " " + interfaceMethodInfo(pool, iter.u16bitAt(pos + 1));
+            case 186:
+                throw new RuntimeException("Bad opcode 186");
+            case NEW:
+                return opstring + " " + classInfo(pool, iter.u16bitAt(pos + 1));
+            case NEWARRAY:
+                return opstring + " " + arrayInfo(iter.byteAt(pos + 1));
+            case ANEWARRAY:
+                return opstring + " " + classInfo(pool, iter.u16bitAt(pos + 1));
+            case WIDE:
+                return wide(iter, pos);
+            case MULTIANEWARRAY:
+                return opstring + " " + classInfo(pool, iter.u16bitAt(pos + 1));
+            case GOTO_W:
+            case JSR_W:
+                return opstring + " " + (iter.s32bitAt(pos + 1)+ pos);
+            default:
+                return opstring;
+        }
+    }
+
+
+    private static String wide(CodeIterator iter, int pos) {
+        int opcode = iter.byteAt(pos + 1);
+        int index = iter.u16bitAt(pos + 2);
+        switch (opcode) {
+            case ILOAD:
+            case LLOAD:
+            case FLOAD:
+            case DLOAD:
+            case ALOAD:
+            case ISTORE:
+            case LSTORE:
+            case FSTORE:
+            case DSTORE:
+            case ASTORE:
+            case IINC:
+            case RET:
+                return opcodes[opcode] + " " + index;
+            default:
+                throw new RuntimeException("Invalid WIDE operand");
+        }
+    }
+
+
+    private static String arrayInfo(int type) {
+        switch (type) {
+            case T_BOOLEAN:
+                return "boolean";
+            case T_CHAR:
+                return "char";
+            case T_BYTE:
+                return "byte";
+            case T_SHORT:
+                return "short";
+            case T_INT:
+                return "int";
+            case T_LONG:
+                return "long";
+            case T_FLOAT:
+                return "float";
+            case T_DOUBLE:
+                return "double";
+            default:
+                throw new RuntimeException("Invalid array type");
+        }
+    }
+
+
+    private static String classInfo(ConstPool pool, int index) {
+        return "#" + index + " = Class " + pool.getClassInfo(index);
+    }
+
+
+    private static String interfaceMethodInfo(ConstPool pool, int index) {
+        return "#" + index + " = Method "
+                + pool.getInterfaceMethodrefClassName(index) + "."
+                + pool.getInterfaceMethodrefName(index) + "("
+                + pool.getInterfaceMethodrefType(index) + ")";
+    }
+
+    private static String methodInfo(ConstPool pool, int index) {
+        return "#" + index + " = Method "
+                + pool.getMethodrefClassName(index) + "."
+                + pool.getMethodrefName(index) + "("
+                + pool.getMethodrefType(index) + ")";
+    }
+
+
+    private static String fieldInfo(ConstPool pool, int index) {
+        return "#" + index + " = Field "
+            + pool.getFieldrefClassName(index) + "."
+            + pool.getFieldrefName(index) + "("
+            + pool.getFieldrefType(index) + ")";
+    }
+
+
+    private static String lookupSwitch(CodeIterator iter, int pos) {
+        StringBuffer buffer = new StringBuffer("lookupswitch {\n");
+        int index = (pos & ~3) + 4;
+        // default
+        buffer.append("\t\tdefault: ").append(pos + iter.s32bitAt(index)).append("\n");
+        int npairs = iter.s32bitAt(index += 4);
+        int end = npairs * 8 + (index += 4);
+
+        for (; index < end; index += 8) {
+            int match = iter.s32bitAt(index);
+            int target = iter.s32bitAt(index + 4) + pos;
+            buffer.append("\t\t").append(match).append(": ").append(target).append("\n");
+        }
+
+        buffer.setCharAt(buffer.length() - 1, '}');
+        return buffer.toString();
+    }
+
+
+    private static String tableSwitch(CodeIterator iter, int pos) {
+        StringBuffer buffer = new StringBuffer("tableswitch {\n");
+        int index = (pos & ~3) + 4;
+        // default
+        buffer.append("\t\tdefault: ").append(pos + iter.s32bitAt(index)).append("\n");
+        int low = iter.s32bitAt(index += 4);
+        int high = iter.s32bitAt(index += 4);
+        int end = (high - low + 1) * 4 + (index += 4);
+
+        // Offset table
+        for (int key = low; index < end; index += 4, key++) {
+            int target = iter.s32bitAt(index) + pos;
+            buffer.append("\t\t").append(key).append(": ").append(target).append("\n");
+        }
+
+        buffer.setCharAt(buffer.length() - 1, '}');
+        return buffer.toString();
+    }
+
+
+    private static String ldc(ConstPool pool, int index) {
+        int tag = pool.getTag(index);
+        switch (tag) {
+            case ConstPool.CONST_String:
+                return "#" + index + " = \"" + pool.getStringInfo(index) + "\"";
+            case ConstPool.CONST_Integer:
+                return "#" + index + " = int " + pool.getIntegerInfo(index);
+            case ConstPool.CONST_Float:
+                return "#" + index + " = float " + pool.getFloatInfo(index);
+            case ConstPool.CONST_Long:
+                return "#" + index + " = long " + pool.getLongInfo(index);
+            case ConstPool.CONST_Double:
+                return "#" + index + " = int " + pool.getDoubleInfo(index);
+            case ConstPool.CONST_Class:
+                return classInfo(pool, index);
+            default:
+                throw new RuntimeException("bad LDC: " + tag);
+        }
+    }
+}
diff --git a/src/main/javassist/bytecode/analysis/Analyzer.java b/src/main/javassist/bytecode/analysis/Analyzer.java
new file mode 100644 (file)
index 0000000..7ef0b00
--- /dev/null
@@ -0,0 +1,422 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License.  Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+import java.util.Iterator;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.CtMethod;
+import javassist.NotFoundException;
+import javassist.bytecode.AccessFlag;
+import javassist.bytecode.BadBytecode;
+import javassist.bytecode.CodeAttribute;
+import javassist.bytecode.CodeIterator;
+import javassist.bytecode.ConstPool;
+import javassist.bytecode.Descriptor;
+import javassist.bytecode.ExceptionTable;
+import javassist.bytecode.MethodInfo;
+import javassist.bytecode.Opcode;
+
+/**
+ * A data-flow analyzer that determines the type state of the stack and local
+ * variable table at every reachable instruction in a method. During analysis,
+ * bytecode verification is performed in a similar manner to that described
+ * in the JVM specification.
+ *
+ * <p>Example:</p>
+ *
+ * <pre>
+ * // Method to analyze
+ * public Object doSomething(int x) {
+ *     Number n;
+ *     if (x < 5) {
+ *        n = new Double(0);
+ *     } else {
+ *        n = new Long(0);
+ *     }
+ *
+ *     return n;
+ * }
+ *
+ * // Which compiles to:
+ * // 0:   iload_1
+ * // 1:   iconst_5
+ * // 2:   if_icmpge   17
+ * // 5:   new #18; //class java/lang/Double
+ * // 8:   dup
+ * // 9:   dconst_0
+ * // 10:  invokespecial   #44; //Method java/lang/Double."<init>":(D)V
+ * // 13:  astore_2
+ * // 14:  goto    26
+ * // 17:  new #16; //class java/lang/Long
+ * // 20:  dup
+ * // 21:  lconst_1
+ * // 22:  invokespecial   #47; //Method java/lang/Long."<init>":(J)V
+ * // 25:  astore_2
+ * // 26:  aload_2
+ * // 27:  areturn
+ *
+ * public void analyzeIt(CtClass clazz, MethodInfo method) {
+ *     Analyzer analyzer = new Analyzer();
+ *     Frame[] frames = analyzer.analyze(clazz, method);
+ *     frames[0].getLocal(0).getCtClass(); // returns clazz;
+ *     frames[0].getLocal(1).getCtClass(); // returns java.lang.String
+ *     frames[1].peek(); // returns Type.INTEGER
+ *     frames[27].peek().getCtClass(); // returns java.lang.Number
+ * }
+ * </pre>
+ *
+ * @see FramePrinter
+ * @author Jason T. Greene
+ */
+public class Analyzer implements Opcode {
+    private final SubroutineScanner scanner = new SubroutineScanner();
+    private CtClass clazz;
+    private ExceptionInfo[] exceptions;
+    private Frame[] frames;
+    private Subroutine[] subroutines;
+
+    private static class ExceptionInfo {
+        private int end;
+        private int handler;
+        private int start;
+        private Type type;
+
+        private ExceptionInfo(int start, int end, int handler, Type type) {
+            this.start = start;
+            this.end = end;
+            this.handler = handler;
+            this.type = type;
+        }
+    }
+
+    /**
+     * Performs data-flow analysis on a method and returns an array, indexed by
+     * instruction position, containing the starting frame state of all reachable
+     * instructions. Non-reachable code, and illegal code offsets are represented
+     * as a null in the frame state array. This can be used to detect dead code.
+     *
+     * If the method does not contain code (it is either native or abstract), null
+     * is returned.
+     *
+     * @param clazz the declaring class of the method
+     * @param method the method to analyze
+     * @return an array, indexed by instruction position, of the starting frame state,
+     *         or null if this method doesn't have code
+     * @throws BadBytecode if the bytecode does not comply with the JVM specification
+     */
+    public Frame[] analyze(CtClass clazz, MethodInfo method) throws BadBytecode {
+        this.clazz = clazz;
+        CodeAttribute codeAttribute = method.getCodeAttribute();
+        // Native or Abstract
+        if (codeAttribute == null)
+            return null;
+
+        int maxLocals = codeAttribute.getMaxLocals();
+        int maxStack = codeAttribute.getMaxStack();
+        int codeLength = codeAttribute.getCodeLength();
+
+        CodeIterator iter = codeAttribute.iterator();
+        IntQueue queue = new IntQueue();
+
+        exceptions = buildExceptionInfo(method);
+        subroutines = scanner.scan(method);
+
+        Executor executor = new Executor(clazz.getClassPool(), method.getConstPool());
+        frames = new Frame[codeLength];
+        frames[iter.lookAhead()] = firstFrame(method, maxLocals, maxStack);
+        queue.add(iter.next());
+        while (!queue.isEmpty()) {
+            analyzeNextEntry(method, iter, queue, executor);
+        }
+
+        return frames;
+    }
+
+    /**
+     * Performs data-flow analysis on a method and returns an array, indexed by
+     * instruction position, containing the starting frame state of all reachable
+     * instructions. Non-reachable code, and illegal code offsets are represented
+     * as a null in the frame state array. This can be used to detect dead code.
+     *
+     * If the method does not contain code (it is either native or abstract), null
+     * is returned.
+     *
+     * @param method the method to analyze
+     * @return an array, indexed by instruction position, of the starting frame state,
+     *         or null if this method doesn't have code
+     * @throws BadBytecode if the bytecode does not comply with the JVM specification
+     */
+    public Frame[] analyze(CtMethod method) throws BadBytecode {
+        return analyze(method.getDeclaringClass(), method.getMethodInfo2());
+    }
+
+    private void analyzeNextEntry(MethodInfo method, CodeIterator iter,
+            IntQueue queue, Executor executor) throws BadBytecode {
+        int pos = queue.take();
+        iter.move(pos);
+        iter.next();
+
+        Frame frame = frames[pos].copy();
+        Subroutine subroutine = subroutines[pos];
+
+        try {
+            executor.execute(method, pos, iter, frame, subroutine);
+        } catch (RuntimeException e) {
+            throw new BadBytecode(e.getMessage() + "[pos = " + pos + "]", e);
+        }
+
+        int opcode = iter.byteAt(pos);
+
+        if (opcode == TABLESWITCH) {
+            mergeTableSwitch(queue, pos, iter, frame);
+        } else if (opcode == LOOKUPSWITCH) {
+            mergeLookupSwitch(queue, pos, iter, frame);
+        } else if (opcode == RET) {
+            mergeRet(queue, iter, pos, frame, subroutine);
+        } else if (Util.isJumpInstruction(opcode)) {
+            int target = Util.getJumpTarget(pos, iter);
+
+            if (Util.isJsr(opcode)) {
+                // Merge the state before the jsr into the next instruction
+                mergeJsr(queue, frames[pos], subroutines[target], pos, lookAhead(iter, pos));
+            } else if (! Util.isGoto(opcode)) {
+                merge(queue, frame, lookAhead(iter, pos));
+            }
+
+            merge(queue, frame, target);
+        } else if (opcode != ATHROW && ! Util.isReturn(opcode)) {
+            // Can advance to next instruction
+            merge(queue, frame, lookAhead(iter, pos));
+        }
+
+        // Merge all exceptions that are reachable from this instruction.
+        // The redundancy is intentional, since the state must be based
+        // on the current instruction frame.
+        mergeExceptionHandlers(queue, method, pos, frame);
+    }
+
+    private ExceptionInfo[] buildExceptionInfo(MethodInfo method) {
+        ConstPool constPool = method.getConstPool();
+        ClassPool classes = clazz.getClassPool();
+
+        ExceptionTable table = method.getCodeAttribute().getExceptionTable();
+        ExceptionInfo[] exceptions = new ExceptionInfo[table.size()];
+        for (int i = 0; i < table.size(); i++) {
+            int index = table.catchType(i);
+            Type type;
+            try {
+                type = index == 0 ? Type.THROWABLE : Type.get(classes.get(constPool.getClassInfo(index)));
+            } catch (NotFoundException e) {
+                throw new IllegalStateException(e);
+            }
+
+            exceptions[i] = new ExceptionInfo(table.startPc(i), table.endPc(i), table.handlerPc(i), type);
+        }
+
+        return exceptions;
+    }
+
+    private Frame firstFrame(MethodInfo method, int maxLocals, int maxStack) {
+        int pos = 0;
+
+        Frame first = new Frame(maxLocals, maxStack);
+        if ((method.getAccessFlags() & AccessFlag.STATIC) == 0) {
+            first.setLocal(pos++, Type.get(clazz));
+        }
+
+        CtClass[] parameters;
+        try {
+            parameters = Descriptor.getParameterTypes(method.getDescriptor(), clazz.getClassPool());
+        } catch (NotFoundException e) {
+            throw new RuntimeException(e);
+        }
+
+        for (int i = 0; i < parameters.length; i++) {
+            Type type = zeroExtend(Type.get(parameters[i]));
+            first.setLocal(pos++, type);
+            if (type.getSize() == 2)
+                first.setLocal(pos++, Type.TOP);
+        }
+
+        return first;
+    }
+
+    private int getNext(CodeIterator iter, int of, int restore) throws BadBytecode {
+        iter.move(of);
+        iter.next();
+        int next = iter.lookAhead();
+        iter.move(restore);
+        iter.next();
+
+        return next;
+    }
+
+    private int lookAhead(CodeIterator iter, int pos) throws BadBytecode {
+        if (! iter.hasNext())
+            throw new BadBytecode("Execution falls off end! [pos = " + pos + "]");
+
+        return iter.lookAhead();
+    }
+
+
+    private void merge(IntQueue queue, Frame frame, int target) {
+        Frame old = frames[target];
+        boolean changed;
+
+        if (old == null) {
+            frames[target] = frame.copy();
+            changed = true;
+        } else {
+            changed = old.merge(frame);
+        }
+
+        if (changed) {
+            queue.add(target);
+        }
+    }
+
+    private void mergeExceptionHandlers(IntQueue queue, MethodInfo method, int pos, Frame frame) {
+        for (int i = 0; i < exceptions.length; i++) {
+            ExceptionInfo exception = exceptions[i];
+
+            // Start is inclusive, while end is exclusive!
+            if (pos >= exception.start && pos < exception.end) {
+                Frame newFrame = frame.copy();
+                newFrame.clearStack();
+                newFrame.push(exception.type);
+                merge(queue, newFrame, exception.handler);
+            }
+        }
+    }
+
+    private void mergeJsr(IntQueue queue, Frame frame, Subroutine sub, int pos, int next) throws BadBytecode {
+        if (sub == null)
+            throw new BadBytecode("No subroutine at jsr target! [pos = " + pos + "]");
+
+        Frame old = frames[next];
+        boolean changed = false;
+
+        if (old == null) {
+            old = frames[next] = frame.copy();
+            changed = true;
+        } else {
+            for (int i = 0; i < frame.localsLength(); i++) {
+                // Skip everything accessed by a subroutine, mergeRet must handle this
+                if (!sub.isAccessed(i)) {
+                    Type oldType = old.getLocal(i);
+                    Type newType = frame.getLocal(i);
+                    if (oldType == null) {
+                        old.setLocal(i, newType);
+                        changed = true;
+                        continue;
+                    }
+
+                    newType = oldType.merge(newType);
+                    // Always set the type, in case a multi-type switched to a standard type.
+                    old.setLocal(i, newType);
+                    if (!newType.equals(oldType) || newType.popChanged())
+                        changed = true;
+                }
+            }
+        }
+
+        if (! old.isJsrMerged()) {
+            old.setJsrMerged(true);
+            changed = true;
+        }
+
+        if (changed && old.isRetMerged())
+            queue.add(next);
+
+    }
+
+    private void mergeLookupSwitch(IntQueue queue, int pos, CodeIterator iter, Frame frame) throws BadBytecode {
+        int index = (pos & ~3) + 4;
+        // default
+        merge(queue, frame, pos + iter.s32bitAt(index));
+        int npairs = iter.s32bitAt(index += 4);
+        int end = npairs * 8 + (index += 4);
+
+        // skip "match"
+        for (index += 4; index < end; index += 8) {
+            int target = iter.s32bitAt(index) + pos;
+            merge(queue, frame, target);
+        }
+    }
+
+    private void mergeRet(IntQueue queue, CodeIterator iter, int pos, Frame frame, Subroutine subroutine) throws BadBytecode {
+        if (subroutine == null)
+            throw new BadBytecode("Ret on no subroutine! [pos = " + pos + "]");
+
+        Iterator callerIter = subroutine.callers().iterator();
+        while (callerIter.hasNext()) {
+            int caller = ((Integer) callerIter.next()).intValue();
+            int returnLoc = getNext(iter, caller, pos);
+            boolean changed = false;
+
+            Frame old = frames[returnLoc];
+            if (old == null) {
+                old = frames[returnLoc] = frame.copyStack();
+                changed = true;
+            } else {
+                changed = old.mergeStack(frame);
+            }
+
+            for (Iterator i = subroutine.accessed().iterator(); i.hasNext(); ) {
+                int index = ((Integer)i.next()).intValue();
+                Type oldType = old.getLocal(index);
+                Type newType = frame.getLocal(index);
+                if (oldType != newType) {
+                    old.setLocal(index, newType);
+                    changed = true;
+                }
+            }
+
+            if (! old.isRetMerged()) {
+                old.setRetMerged(true);
+                changed = true;
+            }
+
+            if (changed && old.isJsrMerged())
+                queue.add(returnLoc);
+        }
+    }
+
+
+    private void mergeTableSwitch(IntQueue queue, int pos, CodeIterator iter, Frame frame) throws BadBytecode {
+        // Skip 4 byte alignment padding
+        int index = (pos & ~3) + 4;
+        // default
+        merge(queue, frame, pos + iter.s32bitAt(index));
+        int low = iter.s32bitAt(index += 4);
+        int high = iter.s32bitAt(index += 4);
+        int end = (high - low + 1) * 4 + (index += 4);
+
+        // Offset table
+        for (; index < end; index += 4) {
+            int target = iter.s32bitAt(index) + pos;
+            merge(queue, frame, target);
+        }
+    }
+
+    private Type zeroExtend(Type type) {
+        if (type == Type.SHORT || type == Type.BYTE || type == Type.CHAR || type == Type.BOOLEAN)
+            return  Type.INTEGER;
+
+        return type;
+    }
+}
diff --git a/src/main/javassist/bytecode/analysis/Executor.java b/src/main/javassist/bytecode/analysis/Executor.java
new file mode 100644 (file)
index 0000000..7cf9a67
--- /dev/null
@@ -0,0 +1,1012 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License.  Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.NotFoundException;
+import javassist.bytecode.BadBytecode;
+import javassist.bytecode.CodeIterator;
+import javassist.bytecode.ConstPool;
+import javassist.bytecode.Descriptor;
+import javassist.bytecode.MethodInfo;
+import javassist.bytecode.Opcode;
+
+/**
+ * Executor is responsible for modeling the effects of a JVM instruction on a frame.
+ *
+ * @author Jason T. Greene
+ */
+public class Executor implements Opcode {
+    private final ConstPool constPool;
+    private final ClassPool classPool;
+    private final Type STRING_TYPE;
+    private final Type CLASS_TYPE;
+    private final Type THROWABLE_TYPE;
+    private int lastPos;
+
+    public Executor(ClassPool classPool, ConstPool constPool) {
+        this.constPool = constPool;
+        this.classPool = classPool;
+
+        try {
+            STRING_TYPE = getType("java.lang.String");
+            CLASS_TYPE = getType("java.lang.Class");
+            THROWABLE_TYPE = getType("java.lang.Throwable");
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    /**
+     * Execute the instruction, modeling the effects on the specified frame and subroutine.
+     * If a subroutine is passed, the access flags will be modified if this instruction accesses
+     * the local variable table.
+     *
+     * @param method the method containing the instruction
+     * @param pos the position of the instruction in the method
+     * @param iter the code iterator used to find the instruction
+     * @param frame the frame to modify to represent the result of the instruction
+     * @param subroutine the optional subroutine this instruction belongs to.
+     * @throws BadBytecode if the bytecode violates the jvm spec
+     */
+    public void execute(MethodInfo method, int pos, CodeIterator iter, Frame frame, Subroutine subroutine) throws BadBytecode {
+        this.lastPos = pos;
+        int opcode = iter.byteAt(pos);
+
+
+        // Declared opcode in order
+        switch (opcode) {
+            case NOP:
+                break;
+            case ACONST_NULL:
+                frame.push(Type.UNINIT);
+                break;
+            case ICONST_M1:
+            case ICONST_0:
+            case ICONST_1:
+            case ICONST_2:
+            case ICONST_3:
+            case ICONST_4:
+            case ICONST_5:
+                frame.push(Type.INTEGER);
+                break;
+            case LCONST_0:
+            case LCONST_1:
+                frame.push(Type.LONG);
+                frame.push(Type.TOP);
+                break;
+            case FCONST_0:
+            case FCONST_1:
+            case FCONST_2:
+                frame.push(Type.FLOAT);
+                break;
+            case DCONST_0:
+            case DCONST_1:
+                frame.push(Type.DOUBLE);
+                frame.push(Type.TOP);
+                break;
+            case BIPUSH:
+            case SIPUSH:
+                frame.push(Type.INTEGER);
+                break;
+            case LDC:
+                evalLDC(iter.byteAt(pos + 1),  frame);
+                break;
+            case LDC_W :
+            case LDC2_W :
+                evalLDC(iter.u16bitAt(pos + 1), frame);
+                break;
+            case ILOAD:
+                evalLoad(Type.INTEGER, iter.byteAt(pos + 1), frame, subroutine);
+                break;
+            case LLOAD:
+                evalLoad(Type.LONG, iter.byteAt(pos + 1), frame, subroutine);
+                break;
+            case FLOAD:
+                evalLoad(Type.FLOAT, iter.byteAt(pos + 1), frame, subroutine);
+                break;
+            case DLOAD:
+                evalLoad(Type.DOUBLE, iter.byteAt(pos + 1), frame, subroutine);
+                break;
+            case ALOAD:
+                evalLoad(Type.OBJECT, iter.byteAt(pos + 1), frame, subroutine);
+                break;
+            case ILOAD_0:
+            case ILOAD_1:
+            case ILOAD_2:
+            case ILOAD_3:
+                evalLoad(Type.INTEGER, opcode - ILOAD_0, frame, subroutine);
+                break;
+            case LLOAD_0:
+            case LLOAD_1:
+            case LLOAD_2:
+            case LLOAD_3:
+                evalLoad(Type.LONG, opcode - LLOAD_0, frame, subroutine);
+                break;
+            case FLOAD_0:
+            case FLOAD_1:
+            case FLOAD_2:
+            case FLOAD_3:
+                evalLoad(Type.FLOAT, opcode - FLOAD_0, frame, subroutine);
+                break;
+            case DLOAD_0:
+            case DLOAD_1:
+            case DLOAD_2:
+            case DLOAD_3:
+                evalLoad(Type.DOUBLE, opcode - DLOAD_0, frame, subroutine);
+                break;
+            case ALOAD_0:
+            case ALOAD_1:
+            case ALOAD_2:
+            case ALOAD_3:
+                evalLoad(Type.OBJECT, opcode - ALOAD_0, frame, subroutine);
+                break;
+            case IALOAD:
+                evalArrayLoad(Type.INTEGER, frame);
+                break;
+            case LALOAD:
+                evalArrayLoad(Type.LONG, frame);
+                break;
+            case FALOAD:
+                evalArrayLoad(Type.FLOAT, frame);
+                break;
+            case DALOAD:
+                evalArrayLoad(Type.DOUBLE, frame);
+                break;
+            case AALOAD:
+                evalArrayLoad(Type.OBJECT, frame);
+                break;
+            case BALOAD:
+            case CALOAD:
+            case SALOAD:
+                evalArrayLoad(Type.INTEGER, frame);
+                break;
+            case ISTORE:
+                evalStore(Type.INTEGER, iter.byteAt(pos + 1), frame, subroutine);
+                break;
+            case LSTORE:
+                evalStore(Type.LONG, iter.byteAt(pos + 1), frame, subroutine);
+                break;
+            case FSTORE:
+                evalStore(Type.FLOAT, iter.byteAt(pos + 1), frame, subroutine);
+                break;
+            case DSTORE:
+                evalStore(Type.DOUBLE, iter.byteAt(pos + 1), frame, subroutine);
+                break;
+            case ASTORE:
+                evalStore(Type.OBJECT, iter.byteAt(pos + 1), frame, subroutine);
+                break;
+            case ISTORE_0:
+            case ISTORE_1:
+            case ISTORE_2:
+            case ISTORE_3:
+                evalStore(Type.INTEGER, opcode - ISTORE_0, frame, subroutine);
+                break;
+            case LSTORE_0:
+            case LSTORE_1:
+            case LSTORE_2:
+            case LSTORE_3:
+                evalStore(Type.LONG, opcode - LSTORE_0, frame, subroutine);
+                break;
+            case FSTORE_0:
+            case FSTORE_1:
+            case FSTORE_2:
+            case FSTORE_3:
+                evalStore(Type.FLOAT, opcode - FSTORE_0, frame, subroutine);
+                break;
+            case DSTORE_0:
+            case DSTORE_1:
+            case DSTORE_2:
+            case DSTORE_3:
+                evalStore(Type.DOUBLE, opcode - DSTORE_0, frame, subroutine);
+                break;
+            case ASTORE_0:
+            case ASTORE_1:
+            case ASTORE_2:
+            case ASTORE_3:
+                evalStore(Type.OBJECT, opcode - ASTORE_0, frame, subroutine);
+                break;
+            case IASTORE:
+                evalArrayStore(Type.INTEGER, frame);
+                break;
+            case LASTORE:
+                evalArrayStore(Type.LONG, frame);
+                break;
+            case FASTORE:
+                evalArrayStore(Type.FLOAT, frame);
+                break;
+            case DASTORE:
+                evalArrayStore(Type.DOUBLE, frame);
+                break;
+            case AASTORE:
+                evalArrayStore(Type.OBJECT, frame);
+                break;
+            case BASTORE:
+            case CASTORE:
+            case SASTORE:
+                evalArrayStore(Type.INTEGER, frame);
+                break;
+            case POP:
+                if (frame.pop() == Type.TOP)
+                    throw new BadBytecode("POP can not be used with a category 2 value, pos = " + pos);
+                break;
+            case POP2:
+                frame.pop();
+                frame.pop();
+                break;
+            case DUP: {
+                Type type = frame.peek();
+                if (type == Type.TOP)
+                    throw new BadBytecode("DUP can not be used with a category 2 value, pos = " + pos);
+
+                frame.push(frame.peek());
+                break;
+            }
+            case DUP_X1:
+            case DUP_X2: {
+                Type type = frame.peek();
+                if (type == Type.TOP)
+                    throw new BadBytecode("DUP can not be used with a category 2 value, pos = " + pos);
+                int end = frame.getTopIndex();
+                int insert = end - (opcode - DUP_X1) - 1;
+                frame.push(type);
+
+                while (end > insert) {
+                    frame.setStack(end, frame.getStack(end - 1));
+                    end--;
+                }
+                frame.setStack(insert, type);
+                break;
+            }
+            case DUP2:
+                frame.push(frame.getStack(frame.getTopIndex() - 1));
+                frame.push(frame.getStack(frame.getTopIndex() - 1));
+                break;
+            case DUP2_X1:
+            case DUP2_X2: {
+                int end = frame.getTopIndex();
+                int insert = end - (opcode - DUP2_X1) - 1;
+                Type type1 = frame.getStack(frame.getTopIndex() - 1);
+                Type type2 = frame.peek();
+                 frame.push(type1);
+                 frame.push(type2);
+                while (end > insert) {
+                    frame.setStack(end, frame.getStack(end - 2));
+                    end--;
+                }
+                frame.setStack(insert, type2);
+                frame.setStack(insert - 1, type1);
+                break;
+            }
+            case SWAP: {
+                Type type1 = frame.pop();
+                Type type2 = frame.pop();
+                if (type1.getSize() == 2 || type2.getSize() == 2)
+                    throw new BadBytecode("Swap can not be used with category 2 values, pos = " + pos);
+                frame.push(type1);
+                frame.push(type2);
+                break;
+            }
+
+            // Math
+            case IADD:
+                evalBinaryMath(Type.INTEGER, frame);
+                break;
+            case LADD:
+                evalBinaryMath(Type.LONG, frame);
+                break;
+            case FADD:
+                evalBinaryMath(Type.FLOAT, frame);
+                break;
+            case DADD:
+                evalBinaryMath(Type.DOUBLE, frame);
+                break;
+            case ISUB:
+                evalBinaryMath(Type.INTEGER, frame);
+                break;
+            case LSUB:
+                evalBinaryMath(Type.LONG, frame);
+                break;
+            case FSUB:
+                evalBinaryMath(Type.FLOAT, frame);
+                break;
+            case DSUB:
+                evalBinaryMath(Type.DOUBLE, frame);
+                break;
+            case IMUL:
+                evalBinaryMath(Type.INTEGER, frame);
+                break;
+            case LMUL:
+                evalBinaryMath(Type.LONG, frame);
+                break;
+            case FMUL:
+                evalBinaryMath(Type.FLOAT, frame);
+                break;
+            case DMUL:
+                evalBinaryMath(Type.DOUBLE, frame);
+                break;
+            case IDIV:
+                evalBinaryMath(Type.INTEGER, frame);
+                break;
+            case LDIV:
+                evalBinaryMath(Type.LONG, frame);
+                break;
+            case FDIV:
+                evalBinaryMath(Type.FLOAT, frame);
+                break;
+            case DDIV:
+                evalBinaryMath(Type.DOUBLE, frame);
+                break;
+            case IREM:
+                evalBinaryMath(Type.INTEGER, frame);
+                break;
+            case LREM:
+                evalBinaryMath(Type.LONG, frame);
+                break;
+            case FREM:
+                evalBinaryMath(Type.FLOAT, frame);
+                break;
+            case DREM:
+                evalBinaryMath(Type.DOUBLE, frame);
+                break;
+
+            // Unary
+            case INEG:
+                verifyAssignable(Type.INTEGER, simplePeek(frame));
+                break;
+            case LNEG:
+                verifyAssignable(Type.LONG, simplePeek(frame));
+                break;
+            case FNEG:
+                verifyAssignable(Type.FLOAT, simplePeek(frame));
+                break;
+            case DNEG:
+                verifyAssignable(Type.DOUBLE, simplePeek(frame));
+                break;
+
+            // Shifts
+            case ISHL:
+                evalShift(Type.INTEGER, frame);
+                break;
+            case LSHL:
+                evalShift(Type.LONG, frame);
+                break;
+            case ISHR:
+                evalShift(Type.INTEGER, frame);
+                break;
+            case LSHR:
+                evalShift(Type.LONG, frame);
+                break;
+            case IUSHR:
+                evalShift(Type.INTEGER,frame);
+                break;
+            case LUSHR:
+                evalShift(Type.LONG, frame);
+                break;
+
+            // Bitwise Math
+            case IAND:
+                evalBinaryMath(Type.INTEGER, frame);
+                break;
+            case LAND:
+                evalBinaryMath(Type.LONG, frame);
+                break;
+            case IOR:
+                evalBinaryMath(Type.INTEGER, frame);
+                break;
+            case LOR:
+                evalBinaryMath(Type.LONG, frame);
+                break;
+            case IXOR:
+                evalBinaryMath(Type.INTEGER, frame);
+                break;
+            case LXOR:
+                evalBinaryMath(Type.LONG, frame);
+                break;
+
+            case IINC: {
+                int index = iter.byteAt(pos + 1);
+                verifyAssignable(Type.INTEGER, frame.getLocal(index));
+                access(index, Type.INTEGER, subroutine);
+                break;
+            }
+
+            // Conversion
+            case I2L:
+                verifyAssignable(Type.INTEGER, simplePop(frame));
+                simplePush(Type.LONG, frame);
+                break;
+            case I2F:
+                verifyAssignable(Type.INTEGER, simplePop(frame));
+                simplePush(Type.FLOAT, frame);
+                break;
+            case I2D:
+                verifyAssignable(Type.INTEGER, simplePop(frame));
+                simplePush(Type.DOUBLE, frame);
+                break;
+            case L2I:
+                verifyAssignable(Type.LONG, simplePop(frame));
+                simplePush(Type.INTEGER, frame);
+                break;
+            case L2F:
+                verifyAssignable(Type.LONG, simplePop(frame));
+                simplePush(Type.FLOAT, frame);
+                break;
+            case L2D:
+                verifyAssignable(Type.LONG, simplePop(frame));
+                simplePush(Type.DOUBLE, frame);
+                break;
+            case F2I:
+                verifyAssignable(Type.FLOAT, simplePop(frame));
+                simplePush(Type.INTEGER, frame);
+                break;
+            case F2L:
+                verifyAssignable(Type.FLOAT, simplePop(frame));
+                simplePush(Type.LONG, frame);
+                break;
+            case F2D:
+                verifyAssignable(Type.FLOAT, simplePop(frame));
+                simplePush(Type.DOUBLE, frame);
+                break;
+            case D2I:
+                verifyAssignable(Type.DOUBLE, simplePop(frame));
+                simplePush(Type.INTEGER, frame);
+                break;
+            case D2L:
+                verifyAssignable(Type.DOUBLE, simplePop(frame));
+                simplePush(Type.LONG, frame);
+                break;
+            case D2F:
+                verifyAssignable(Type.DOUBLE, simplePop(frame));
+                simplePush(Type.FLOAT, frame);
+                break;
+            case I2B:
+            case I2C:
+            case I2S:
+                verifyAssignable(Type.INTEGER, frame.peek());
+                break;
+            case LCMP:
+                verifyAssignable(Type.LONG, simplePop(frame));
+                verifyAssignable(Type.LONG, simplePop(frame));
+                frame.push(Type.INTEGER);
+                break;
+            case FCMPL:
+            case FCMPG:
+                verifyAssignable(Type.FLOAT, simplePop(frame));
+                verifyAssignable(Type.FLOAT, simplePop(frame));
+                frame.push(Type.INTEGER);
+                break;
+            case DCMPL:
+            case DCMPG:
+                verifyAssignable(Type.DOUBLE, simplePop(frame));
+                verifyAssignable(Type.DOUBLE, simplePop(frame));
+                frame.push(Type.INTEGER);
+                break;
+
+            // Control flow
+            case IFEQ:
+            case IFNE:
+            case IFLT:
+            case IFGE:
+            case IFGT:
+            case IFLE:
+                verifyAssignable(Type.INTEGER, simplePop(frame));
+                break;
+            case IF_ICMPEQ:
+            case IF_ICMPNE:
+            case IF_ICMPLT:
+            case IF_ICMPGE:
+            case IF_ICMPGT:
+            case IF_ICMPLE:
+                verifyAssignable(Type.INTEGER, simplePop(frame));
+                verifyAssignable(Type.INTEGER, simplePop(frame));
+                break;
+            case IF_ACMPEQ:
+            case IF_ACMPNE:
+                verifyAssignable(Type.OBJECT, simplePop(frame));
+                verifyAssignable(Type.OBJECT, simplePop(frame));
+                break;
+            case GOTO:
+                break;
+            case JSR:
+                frame.push(Type.RETURN_ADDRESS);
+                break;
+            case RET:
+                verifyAssignable(Type.RETURN_ADDRESS, frame.getLocal(iter.byteAt(pos + 1)));
+                break;
+            case TABLESWITCH:
+            case LOOKUPSWITCH:
+            case IRETURN:
+                verifyAssignable(Type.INTEGER, simplePop(frame));
+                break;
+            case LRETURN:
+                verifyAssignable(Type.LONG, simplePop(frame));
+                break;
+            case FRETURN:
+                verifyAssignable(Type.FLOAT, simplePop(frame));
+                break;
+            case DRETURN:
+                verifyAssignable(Type.DOUBLE, simplePop(frame));
+                break;
+            case ARETURN:
+                try {
+                    CtClass returnType = Descriptor.getReturnType(method.getDescriptor(), classPool);
+                    verifyAssignable(Type.get(returnType), simplePop(frame));
+                } catch (NotFoundException e) {
+                   throw new RuntimeException(e);
+                }
+                break;
+            case RETURN:
+                break;
+            case GETSTATIC:
+                evalGetField(opcode, iter.u16bitAt(pos + 1), frame);
+                break;
+            case PUTSTATIC:
+                evalPutField(opcode, iter.u16bitAt(pos + 1), frame);
+                break;
+            case GETFIELD:
+                evalGetField(opcode, iter.u16bitAt(pos + 1), frame);
+                break;
+            case PUTFIELD:
+                evalPutField(opcode, iter.u16bitAt(pos + 1), frame);
+                break;
+            case INVOKEVIRTUAL:
+            case INVOKESPECIAL:
+            case INVOKESTATIC:
+                evalInvokeMethod(opcode, iter.u16bitAt(pos + 1), frame);
+                break;
+            case INVOKEINTERFACE:
+                evalInvokeIntfMethod(opcode, iter.u16bitAt(pos + 1), frame);
+                break;
+            case 186:
+                throw new RuntimeException("Bad opcode 186");
+            case NEW:
+                frame.push(typeFromDesc(constPool.getClassInfo(iter.u16bitAt(pos + 1))));
+                break;
+            case NEWARRAY:
+                evalNewArray(pos, iter, frame);
+                break;
+            case ANEWARRAY:
+                evalNewObjectArray(pos, iter, frame);
+                break;
+            case ARRAYLENGTH: {
+                Type array = simplePop(frame);
+                if (! array.isArray() && array != Type.UNINIT)
+                    throw new BadBytecode("Array length passed a non-array [pos = " + pos + "]: " + array);
+                frame.push(Type.INTEGER);
+                break;
+            }
+            case ATHROW:
+                verifyAssignable(THROWABLE_TYPE, simplePop(frame));
+                break;
+            case CHECKCAST:
+                verifyAssignable(Type.OBJECT, simplePop(frame));
+                frame.push(typeFromDesc(constPool.getClassInfo(iter.u16bitAt(pos + 1))));
+                break;
+            case INSTANCEOF:
+                verifyAssignable(Type.OBJECT, simplePop(frame));
+                frame.push(Type.INTEGER);
+                break;
+            case MONITORENTER:
+            case MONITOREXIT:
+                verifyAssignable(Type.OBJECT, simplePop(frame));
+                break;
+            case WIDE:
+                evalWide(pos, iter, frame, subroutine);
+                break;
+            case MULTIANEWARRAY:
+                evalNewObjectArray(pos, iter, frame);
+                break;
+            case IFNULL:
+            case IFNONNULL:
+                verifyAssignable(Type.OBJECT, simplePop(frame));
+                break;
+            case GOTO_W:
+                break;
+            case JSR_W:
+                frame.push(Type.RETURN_ADDRESS);
+                break;
+        }
+    }
+
+    private Type zeroExtend(Type type) {
+        if (type == Type.SHORT || type == Type.BYTE || type == Type.CHAR || type == Type.BOOLEAN)
+            return  Type.INTEGER;
+
+        return type;
+    }
+
+    private void evalArrayLoad(Type expectedComponent, Frame frame) throws BadBytecode {
+        Type index = frame.pop();
+        Type array = frame.pop();
+
+        // Special case, an array defined by aconst_null
+        // TODO - we might need to be more inteligent about this
+        if (array == Type.UNINIT) {
+            verifyAssignable(Type.INTEGER, index);
+            if (expectedComponent == Type.OBJECT) {
+                simplePush(Type.UNINIT, frame);
+            } else {
+                simplePush(expectedComponent, frame);
+            }
+            return;
+        }
+
+        Type component = array.getComponent();
+
+        if (component == null)
+            throw new BadBytecode("Not an array! [pos = " + lastPos + "]: " + component);
+
+        component = zeroExtend(component);
+
+        verifyAssignable(expectedComponent, component);
+        verifyAssignable(Type.INTEGER, index);
+        simplePush(component, frame);
+    }
+
+    private void evalArrayStore(Type expectedComponent, Frame frame) throws BadBytecode {
+        Type value = simplePop(frame);
+        Type index = frame.pop();
+        Type array = frame.pop();
+
+        if (array == Type.UNINIT) {
+            verifyAssignable(Type.INTEGER, index);
+            return;
+        }
+
+        Type component = array.getComponent();
+
+        if (component == null)
+            throw new BadBytecode("Not an array! [pos = " + lastPos + "]: " + component);
+
+        component = zeroExtend(component);
+
+        verifyAssignable(expectedComponent, component);
+        verifyAssignable(Type.INTEGER, index);
+
+        // This intentionally only checks for Object on aastore
+        // downconverting of an array (no casts)
+        // e.g. Object[] blah = new String[];
+        //      blah[2] = (Object) "test";
+        //      blah[3] = new Integer(); // compiler doesnt catch it (has legal bytecode),
+        //                               // but will throw arraystoreexception
+        if (expectedComponent == Type.OBJECT) {
+            verifyAssignable(expectedComponent, value);
+        } else {
+            verifyAssignable(component, value);
+        }
+    }
+
+    private void evalBinaryMath(Type expected, Frame frame) throws BadBytecode {
+        Type value2 = simplePop(frame);
+        Type value1 = simplePop(frame);
+
+        verifyAssignable(expected, value2);
+        verifyAssignable(expected, value1);
+        simplePush(value1, frame);
+    }
+
+    private void evalGetField(int opcode, int index, Frame frame) throws BadBytecode {
+        String desc = constPool.getFieldrefType(index);
+        Type type = zeroExtend(typeFromDesc(desc));
+
+        if (opcode == GETFIELD) {
+            Type objectType = typeFromDesc(constPool.getFieldrefClassName(index));
+            verifyAssignable(objectType, simplePop(frame));
+        }
+
+        simplePush(type, frame);
+    }
+
+    private void evalInvokeIntfMethod(int opcode, int index, Frame frame) throws BadBytecode {
+        String desc = constPool.getInterfaceMethodrefType(index);
+        Type[] types = paramTypesFromDesc(desc);
+        int i = types.length;
+
+        while (i > 0)
+            verifyAssignable(zeroExtend(types[--i]), simplePop(frame));
+
+        String classDesc = constPool.getInterfaceMethodrefClassName(index);
+        Type objectType = typeFromDesc(classDesc);
+        verifyAssignable(objectType, simplePop(frame));
+
+        Type returnType = returnTypeFromDesc(desc);
+        if (returnType != Type.VOID)
+            simplePush(zeroExtend(returnType), frame);
+    }
+
+    private void evalInvokeMethod(int opcode, int index, Frame frame) throws BadBytecode {
+        String desc = constPool.getMethodrefType(index);
+        Type[] types = paramTypesFromDesc(desc);
+        int i = types.length;
+
+        while (i > 0)
+            verifyAssignable(zeroExtend(types[--i]), simplePop(frame));
+
+        if (opcode != INVOKESTATIC) {
+            Type objectType = typeFromDesc(constPool.getMethodrefClassName(index));
+            verifyAssignable(objectType, simplePop(frame));
+        }
+
+        Type returnType = returnTypeFromDesc(desc);
+        if (returnType != Type.VOID)
+            simplePush(zeroExtend(returnType), frame);
+    }
+
+
+    private void evalLDC(int index, Frame frame) throws BadBytecode {
+        int tag = constPool.getTag(index);
+        Type type;
+        switch (tag) {
+        case ConstPool.CONST_String:
+            type = STRING_TYPE;
+            break;
+        case ConstPool.CONST_Integer:
+            type = Type.INTEGER;
+            break;
+        case ConstPool.CONST_Float:
+            type = Type.FLOAT;
+            break;
+        case ConstPool.CONST_Long:
+            type = Type.LONG;
+            break;
+        case ConstPool.CONST_Double:
+            type = Type.DOUBLE;
+            break;
+        case ConstPool.CONST_Class:
+            type = CLASS_TYPE;
+            break;
+        default:
+            throw new BadBytecode("bad LDC [pos = " + lastPos + "]: " + tag);
+        }
+
+        simplePush(type, frame);
+    }
+
+    private void evalLoad(Type expected, int index, Frame frame, Subroutine subroutine) throws BadBytecode {
+        Type type = frame.getLocal(index);
+
+        verifyAssignable(expected, type);
+
+        simplePush(type, frame);
+        access(index, type, subroutine);
+    }
+
+    private void evalNewArray(int pos, CodeIterator iter, Frame frame) throws BadBytecode {
+        verifyAssignable(Type.INTEGER, simplePop(frame));
+        Type type = null;
+        int typeInfo = iter.byteAt(pos + 1);
+        switch (typeInfo) {
+            case T_BOOLEAN:
+                type = getType("boolean[]");
+                break;
+            case T_CHAR:
+                type = getType("char[]");
+                break;
+            case T_BYTE:
+                type = getType("byte[]");
+                break;
+            case T_SHORT:
+                type = getType("short[]");
+                break;
+            case T_INT:
+                type = getType("int[]");
+                break;
+            case T_LONG:
+                type = getType("long[]");
+                break;
+            case T_FLOAT:
+                type = getType("float[]");
+                break;
+            case T_DOUBLE:
+                type = getType("double[]");
+                break;
+            default:
+                throw new BadBytecode("Invalid array type [pos = " + pos + "]: " + typeInfo);
+
+        }
+
+        frame.push(type);
+    }
+
+    private void evalNewObjectArray(int pos, CodeIterator iter, Frame frame) throws BadBytecode {
+        // Convert to x[] format
+        Type type = typeFromDesc(constPool.getClassInfo(iter.u16bitAt(pos + 1)));
+        String name = type.getCtClass().getName();
+        int opcode = iter.byteAt(pos);
+        int dimensions;
+
+        if (opcode == MULTIANEWARRAY) {
+            dimensions = iter.byteAt(pos + 3);
+        } else {
+            name = name + "[]";
+            dimensions = 1;
+        }
+
+        while (dimensions-- > 0) {
+            verifyAssignable(Type.INTEGER, simplePop(frame));
+        }
+
+        simplePush(getType(name), frame);
+    }
+
+    private void evalPutField(int opcode, int index, Frame frame) throws BadBytecode {
+        String desc = constPool.getFieldrefType(index);
+        Type type = zeroExtend(typeFromDesc(desc));
+
+        verifyAssignable(type, simplePop(frame));
+
+        if (opcode == PUTFIELD) {
+            Type objectType = typeFromDesc(constPool.getFieldrefClassName(index));
+            verifyAssignable(objectType, simplePop(frame));
+        }
+    }
+
+    private void evalShift(Type expected, Frame frame) throws BadBytecode {
+        Type value2 = simplePop(frame);
+        Type value1 = simplePop(frame);
+
+        verifyAssignable(Type.INTEGER, value2);
+        verifyAssignable(expected, value1);
+        simplePush(value1, frame);
+    }
+
+    private void evalStore(Type expected, int index, Frame frame, Subroutine subroutine) throws BadBytecode {
+        Type type = simplePop(frame);
+
+        // RETURN_ADDRESS is allowed by ASTORE
+        if (! (expected == Type.OBJECT && type == Type.RETURN_ADDRESS))
+            verifyAssignable(expected, type);
+        simpleSetLocal(index, type, frame);
+        access(index, type, subroutine);
+    }
+
+    private void evalWide(int pos, CodeIterator iter, Frame frame, Subroutine subroutine) throws BadBytecode {
+        int opcode = iter.byteAt(pos + 1);
+        int index = iter.u16bitAt(pos + 2);
+        switch (opcode) {
+            case ILOAD:
+                evalLoad(Type.INTEGER, index, frame, subroutine);
+                break;
+            case LLOAD:
+                evalLoad(Type.LONG, index, frame, subroutine);
+                break;
+            case FLOAD:
+                evalLoad(Type.FLOAT, index, frame, subroutine);
+                break;
+            case DLOAD:
+                evalLoad(Type.DOUBLE, index, frame, subroutine);
+                break;
+            case ALOAD:
+                evalLoad(Type.OBJECT, index, frame, subroutine);
+                break;
+            case ISTORE:
+                evalStore(Type.INTEGER, index, frame, subroutine);
+                break;
+            case LSTORE:
+                evalStore(Type.LONG, index, frame, subroutine);
+                break;
+            case FSTORE:
+                evalStore(Type.FLOAT, index, frame, subroutine);
+                break;
+            case DSTORE:
+                evalStore(Type.DOUBLE, index, frame, subroutine);
+                break;
+            case ASTORE:
+                evalStore(Type.OBJECT, index, frame, subroutine);
+                break;
+            case IINC:
+                verifyAssignable(Type.INTEGER, frame.getLocal(index));
+                break;
+            case RET:
+                verifyAssignable(Type.RETURN_ADDRESS, frame.getLocal(index));
+                break;
+            default:
+                throw new BadBytecode("Invalid WIDE operand [pos = " + pos + "]: " + opcode);
+        }
+
+    }
+
+    private Type getType(String name) throws BadBytecode {
+        try {
+            return Type.get(classPool.get(name));
+        } catch (NotFoundException e) {
+            throw new BadBytecode("Could not find class [pos = " + lastPos + "]: " + name);
+        }
+    }
+
+    private Type[] paramTypesFromDesc(String desc) throws BadBytecode {
+        CtClass classes[] = null;
+        try {
+            classes = Descriptor.getParameterTypes(desc, classPool);
+        } catch (NotFoundException e) {
+            throw new BadBytecode("Could not find class in descriptor [pos = " + lastPos + "]: " + e.getMessage());
+        }
+
+        if (classes == null)
+            throw new BadBytecode("Could not obtain parameters for descriptor [pos = " + lastPos + "]: " + desc);
+
+        Type[] types = new Type[classes.length];
+        for (int i = 0; i < types.length; i++)
+            types[i] = Type.get(classes[i]);
+
+        return types;
+    }
+
+    private Type returnTypeFromDesc(String desc) throws BadBytecode {
+        CtClass clazz = null;
+        try {
+            clazz = Descriptor.getReturnType(desc, classPool);
+        } catch (NotFoundException e) {
+            throw new BadBytecode("Could not find class in descriptor [pos = " + lastPos + "]: " + e.getMessage());
+        }
+
+        if (clazz == null)
+            throw new BadBytecode("Could not obtain return type for descriptor [pos = " + lastPos + "]: " + desc);
+
+        return Type.get(clazz);
+    }
+
+    private Type simplePeek(Frame frame) {
+        Type type = frame.peek();
+        return (type == Type.TOP) ? frame.getStack(frame.getTopIndex() - 1) : type;
+    }
+
+    private Type simplePop(Frame frame) {
+        Type type = frame.pop();
+        return (type == Type.TOP) ? frame.pop() : type;
+    }
+
+    private void simplePush(Type type, Frame frame) {
+        frame.push(type);
+        if (type.getSize() == 2)
+            frame.push(Type.TOP);
+    }
+
+    private void access(int index, Type type, Subroutine subroutine) {
+        if (subroutine == null)
+            return;
+        subroutine.access(index);
+        if (type.getSize() == 2)
+            subroutine.access(index + 1);
+    }
+
+    private void simpleSetLocal(int index, Type type, Frame frame) {
+        frame.setLocal(index, type);
+        if (type.getSize() == 2)
+            frame.setLocal(index + 1, Type.TOP);
+    }
+
+    private Type typeFromDesc(String desc) throws BadBytecode {
+        CtClass clazz = null;
+        try {
+            clazz = Descriptor.toCtClass(desc, classPool);
+        } catch (NotFoundException e) {
+            throw new BadBytecode("Could not find class in descriptor [pos = " + lastPos + "]: " + e.getMessage());
+        }
+
+        if (clazz == null)
+            throw new BadBytecode("Could not obtain type for descriptor [pos = " + lastPos + "]: " + desc);
+
+        return Type.get(clazz);
+    }
+
+    private void verifyAssignable(Type expected, Type type) throws BadBytecode {
+        if (! expected.isAssignableFrom(type))
+            throw new BadBytecode("Expected type: " + expected + " Got: " + type + " [pos = " + lastPos + "]");
+    }
+}
diff --git a/src/main/javassist/bytecode/analysis/Frame.java b/src/main/javassist/bytecode/analysis/Frame.java
new file mode 100644 (file)
index 0000000..cf646f4
--- /dev/null
@@ -0,0 +1,288 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License.  Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+
+/**
+ * Represents the stack frame and local variable table at a particular point in time.
+ *
+ * @author Jason T. Greene
+ */
+public class Frame {
+    private Type[] locals;
+    private Type[] stack;
+    private int top;
+    private boolean jsrMerged;
+    private boolean retMerged;
+
+    /**
+     * Create a new frame with the specified local variable table size, and max stack size
+     *
+     * @param locals the number of local variable table entries
+     * @param stack the maximum stack size
+     */
+    public Frame(int locals, int stack) {
+        this.locals = new Type[locals];
+        this.stack = new Type[stack];
+    }
+
+    /**
+     * Returns the local varaible table entry at index.
+     *
+     * @param index the position in the table
+     * @return the type if one exists, or null if the position is empty
+     */
+    public Type getLocal(int index) {
+        return locals[index];
+    }
+
+    /**
+     * Sets the local variable table entry at index to a type.
+     *
+     * @param index the position in the table
+     * @param type the type to set at the position
+     */
+    public void setLocal(int index, Type type) {
+        locals[index] = type;
+    }
+
+
+    /**
+     * Returns the type on the stack at the specified index.
+     *
+     * @param index the position on the stack
+     * @return the type of the stack position
+     */
+    public Type getStack(int index) {
+        return stack[index];
+    }
+
+    /**
+     * Sets the type of the stack position
+     *
+     * @param index the position on the stack
+     * @param type the type to set
+     */
+    public void setStack(int index, Type type) {
+        stack[index] = type;
+    }
+
+    /**
+     * Empties the stack
+     */
+    public void clearStack() {
+        top = 0;
+    }
+
+    /**
+     * Gets the index of the type sitting at the top of the stack.
+     * This is not to be confused with a length operation which
+     * would return the number of elements, not the position of
+     * the last element.
+     *
+     * @return the position of the element at the top of the stack
+     */
+    public int getTopIndex() {
+        return top - 1;
+    }
+
+    /**
+     * Returns the number of local variable table entries, specified
+     * at construction.
+     *
+     * @return the number of local variable table entries
+     */
+    public int localsLength() {
+        return locals.length;
+    }
+
+    /**
+     * Gets the top of the stack without altering it
+     *
+     * @return the top of the stack
+     */
+    public Type peek() {
+        if (top < 1)
+            throw new IndexOutOfBoundsException("Stack is empty");
+
+        return stack[top - 1];
+    }
+
+    /**
+     * Alters the stack to contain one less element and return it.
+     *
+     * @return the element popped from the stack
+     */
+    public Type pop() {
+        if (top < 1)
+            throw new IndexOutOfBoundsException("Stack is empty");
+        return stack[--top];
+    }
+
+    /**
+     * Alters the stack by placing the passed type on the top
+     *
+     * @param type the type to add to the top
+     */
+    public void push(Type type) {
+        stack[top++] = type;
+    }
+
+
+    /**
+     * Makes a shallow copy of this frame, i.e. the type instances will
+     * remain the same.
+     *
+     * @return the shallow copy
+     */
+    public Frame copy() {
+        Frame frame = new Frame(locals.length, stack.length);
+        System.arraycopy(locals, 0, frame.locals, 0, locals.length);
+        System.arraycopy(stack, 0, frame.stack, 0, stack.length);
+        frame.top = top;
+        return frame;
+    }
+
+    /**
+     * Makes a shallow copy of the stack portion of this frame. The local
+     * variable table size will be copied, but its contents will be empty.
+     *
+     * @return the shallow copy of the stack
+     */
+    public Frame copyStack() {
+        Frame frame = new Frame(locals.length, stack.length);
+        System.arraycopy(stack, 0, frame.stack, 0, stack.length);
+        frame.top = top;
+        return frame;
+    }
+
+    /**
+     * Merges all types on the stack of this frame instance with that of the specified frame.
+     * The local variable table is left untouched.
+     *
+     * @param frame the frame to merge the stack from
+     * @return true if any changes where made
+     */
+    public boolean mergeStack(Frame frame) {
+        boolean changed = false;
+        if (top != frame.top)
+            throw new RuntimeException("Operand stacks could not be merged, they are different sizes!");
+
+        for (int i = 0; i < top; i++) {
+            if (stack[i] != null) {
+                Type prev = stack[i];
+                Type merged = prev.merge(frame.stack[i]);
+                if (merged == Type.BOGUS)
+                    throw new RuntimeException("Operand stacks could not be merged due to differing primitive types: pos = " + i);
+
+                stack[i] = merged;
+                // always replace the instance in case a multi-interface type changes to a normal Type
+                if ((! merged.equals(prev)) || merged.popChanged()) {
+                    changed = true;
+                }
+            }
+        }
+
+        return changed;
+    }
+
+    /**
+     * Merges all types on the stack and local variable table of this frame with that of the specified
+     * type.
+     *
+     * @param frame the frame to merge with
+     * @return true if any changes to this frame where made by this merge
+     */
+    public boolean merge(Frame frame) {
+        boolean changed = false;
+
+        // Local variable table
+        for (int i = 0; i < locals.length; i++) {
+            if (locals[i] != null) {
+                Type prev = locals[i];
+                Type merged = prev.merge(frame.locals[i]);
+                // always replace the instance in case a multi-interface type changes to a normal Type
+                locals[i] = merged;
+                if (! merged.equals(prev) || merged.popChanged()) {
+                    changed = true;
+                }
+            } else if (frame.locals[i] != null) {
+                locals[i] = frame.locals[i];
+                changed = true;
+            }
+        }
+
+        changed |= mergeStack(frame);
+        return changed;
+    }
+
+    public String toString() {
+        StringBuffer buffer = new StringBuffer();
+
+        buffer.append("locals = [");
+        for (int i = 0; i < locals.length; i++) {
+            buffer.append(locals[i] == null ? "empty" : locals[i].toString());
+            if (i < locals.length - 1)
+                buffer.append(", ");
+        }
+        buffer.append("] stack = [");
+        for (int i = 0; i < top; i++) {
+            buffer.append(stack[i]);
+            if (i < top - 1)
+                buffer.append(", ");
+        }
+        buffer.append("]");
+
+        return buffer.toString();
+    }
+
+    /**
+     * Whether or not state from the source JSR instruction has been merged
+     *
+     * @return true if JSR state has been merged
+     */
+    boolean isJsrMerged() {
+        return jsrMerged;
+    }
+
+    /**
+     * Sets whether of not the state from the source JSR instruction has been merged
+     *
+     * @param jsrMerged true if merged, otherwise false
+     */
+    void setJsrMerged(boolean jsrMerged) {
+        this.jsrMerged = jsrMerged;
+    }
+
+    /**
+     * Whether or not state from the RET instruction, of the subroutine that was jumped
+     * to has been merged.
+     *
+     * @return true if RET state has been merged
+     */
+    boolean isRetMerged() {
+        return retMerged;
+    }
+
+    /**
+     * Sets whether or not state from the RET instruction, of the subroutine that was jumped
+     * to has been merged.
+     *
+     * @param retMerged true if RET state has been merged
+     */
+    void setRetMerged(boolean retMerged) {
+        this.retMerged = retMerged;
+    }
+}
diff --git a/src/main/javassist/bytecode/analysis/FramePrinter.java b/src/main/javassist/bytecode/analysis/FramePrinter.java
new file mode 100644 (file)
index 0000000..263e529
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License.  Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+import java.io.PrintStream;
+
+import javassist.CtClass;
+import javassist.CtMethod;
+import javassist.Modifier;
+import javassist.NotFoundException;
+import javassist.bytecode.BadBytecode;
+import javassist.bytecode.CodeAttribute;
+import javassist.bytecode.CodeIterator;
+import javassist.bytecode.ConstPool;
+import javassist.bytecode.Descriptor;
+import javassist.bytecode.InstructionPrinter;
+import javassist.bytecode.MethodInfo;
+
+/**
+ * A utility class for printing a merged view of the frame state and the
+ * instructions of a method.
+ *
+ * @author Jason T. Greene
+ */
+public final class FramePrinter {
+    private final PrintStream stream;
+
+    public FramePrinter(PrintStream stream) {
+        this.stream = stream;
+    }
+
+    public static void print(CtClass clazz, PrintStream stream) {
+        (new FramePrinter(stream)).print(clazz);
+    }
+
+    public void print(CtClass clazz) {
+        CtMethod[] methods = clazz.getDeclaredMethods();
+        for (int i = 0; i < methods.length; i++) {
+            print(methods[i]);
+        }
+    }
+
+    private String getMethodString(CtMethod method) {
+        try {
+            return Modifier.toString(method.getModifiers()) + " "
+                    + method.getReturnType().getName() + " " + method.getName()
+                    + Descriptor.toString(method.getSignature()) + ";";
+        } catch (NotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void print(CtMethod method) {
+        stream.println("\n" + getMethodString(method));
+        MethodInfo info = method.getMethodInfo2();
+        ConstPool pool = info.getConstPool();
+        CodeAttribute code = info.getCodeAttribute();
+        if (code == null)
+            return;
+
+        Frame[] frames;
+        try {
+            frames = (new Analyzer()).analyze(method.getDeclaringClass(), info);
+        } catch (BadBytecode e) {
+            throw new RuntimeException(e);
+        }
+
+        int spacing = String.valueOf(code.getCodeLength()).length();
+
+        CodeIterator iterator = code.iterator();
+        while (iterator.hasNext()) {
+            int pos;
+            try {
+                pos = iterator.next();
+            } catch (BadBytecode e) {
+                throw new RuntimeException(e);
+            }
+
+            stream.println(pos + ": " + InstructionPrinter.instructionString(iterator, pos, pool));
+
+            addSpacing(spacing + 3);
+            Frame frame = frames[pos];
+            if (frame == null) {
+                stream.println("--DEAD CODE--");
+                continue;
+            }
+            printStack(frame);
+
+            addSpacing(spacing + 3);
+            printLocals(frame);
+        }
+
+    }
+
+    private void printStack(Frame frame) {
+        stream.print("stack [");
+        int top = frame.getTopIndex();
+        for (int i = 0; i <= top; i++) {
+            if (i > 0)
+                stream.print(", ");
+            Type type = frame.getStack(i);
+            stream.print(type);
+        }
+        stream.println("]");
+    }
+
+    private void printLocals(Frame frame) {
+        stream.print("locals [");
+        int length = frame.localsLength();
+        for (int i = 0; i < length; i++) {
+            if (i > 0)
+                stream.print(", ");
+            Type type = frame.getLocal(i);
+            stream.print(type == null ? "empty" : type.toString());
+        }
+        stream.println("]");
+    }
+
+    private void addSpacing(int count) {
+        while (count-- > 0)
+            stream.print(' ');
+    }
+
+}
diff --git a/src/main/javassist/bytecode/analysis/IntQueue.java b/src/main/javassist/bytecode/analysis/IntQueue.java
new file mode 100644 (file)
index 0000000..d50cddc
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License.  Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+import java.util.NoSuchElementException;
+
+class IntQueue {
+    private static class Entry {
+        private IntQueue.Entry next;
+        private int value;
+        private Entry(int value) {
+            this.value = value;
+        }
+    }
+    private IntQueue.Entry head;
+
+    private IntQueue.Entry tail;
+
+    void add(int value) {
+        IntQueue.Entry entry = new Entry(value);
+        if (tail != null)
+            tail.next = entry;
+        tail = entry;
+
+        if (head == null)
+            head = entry;
+    }
+
+    boolean isEmpty() {
+        return head == null;
+    }
+
+    int take() {
+        if (head == null)
+            throw new NoSuchElementException();
+
+        int value = head.value;
+        head = head.next;
+        if (head == null)
+            tail = null;
+
+        return value;
+    }
+}
\ No newline at end of file
diff --git a/src/main/javassist/bytecode/analysis/MultiArrayType.java b/src/main/javassist/bytecode/analysis/MultiArrayType.java
new file mode 100644 (file)
index 0000000..750116c
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License.  Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.NotFoundException;
+
+/**
+ * Represents an array of {@link MultiType} instances.
+ *
+ * @author Jason T. Greene
+ */
+public class MultiArrayType extends Type {
+    private MultiType component;
+    private int dims;
+
+    public MultiArrayType(MultiType component, int dims) {
+        super(null);
+        this.component = component;
+        this.dims = dims;
+    }
+
+    public CtClass getCtClass() {
+        CtClass clazz = component.getCtClass();
+        if (clazz == null)
+            return null;
+
+        ClassPool pool = clazz.getClassPool();
+        if (pool == null)
+            pool = ClassPool.getDefault();
+
+        String name = arrayName(clazz.getName(), dims);
+
+        try {
+            return pool.get(name);
+        } catch (NotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    boolean popChanged() {
+        return component.popChanged();
+    }
+
+    public int getDimensions() {
+        return dims;
+    }
+
+    public Type getComponent() {
+       return dims == 1 ? (Type)component : new MultiArrayType(component, dims - 1);
+    }
+
+    public int getSize() {
+        return 1;
+    }
+
+    public boolean isArray() {
+        return true;
+    }
+
+    public boolean isAssignableFrom(Type type) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public boolean isReference() {
+       return true;
+    }
+
+    public boolean isAssignableTo(Type type) {
+        if (eq(type.getCtClass(), Type.OBJECT.getCtClass()))
+            return true;
+
+        if (eq(type.getCtClass(), Type.CLONEABLE.getCtClass()))
+            return true;
+
+        if (eq(type.getCtClass(), Type.SERIALIZABLE.getCtClass()))
+            return true;
+
+        if (! type.isArray())
+            return false;
+
+        Type typeRoot = getRootComponent(type);
+        int typeDims = type.getDimensions();
+
+        if (typeDims > dims)
+            return false;
+
+        if (typeDims < dims) {
+            if (eq(typeRoot.getCtClass(), Type.OBJECT.getCtClass()))
+                return true;
+
+            if (eq(typeRoot.getCtClass(), Type.CLONEABLE.getCtClass()))
+                return true;
+
+            if (eq(typeRoot.getCtClass(), Type.SERIALIZABLE.getCtClass()))
+                return true;
+
+            return false;
+        }
+
+        return component.isAssignableTo(typeRoot);
+    }
+
+    public boolean equals(Object o) {
+        if (! (o instanceof MultiArrayType))
+            return false;
+        MultiArrayType multi = (MultiArrayType)o;
+
+        return component.equals(multi.component) && dims == multi.dims;
+    }
+
+    public String toString() {
+        // follows the same detailed formating scheme as component
+        return arrayName(component.toString(), dims);
+    }
+}
diff --git a/src/main/javassist/bytecode/analysis/MultiType.java b/src/main/javassist/bytecode/analysis/MultiType.java
new file mode 100644 (file)
index 0000000..31486ea
--- /dev/null
@@ -0,0 +1,315 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License.  Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javassist.CtClass;
+
+/**
+ * MultiType represents an unresolved type. Whenever two <literal>Type</literal>
+ * instances are merged, if they share more than one super type (either an
+ * interface or a superclass), then a <literal>MultiType</literal> is used to
+ * represent the possible super types. The goal of a <literal>MultiType</literal>
+ * is to reduce the set of possible types down to a single resolved type. This
+ * is done by eliminating non-assignable types from the typeset when the
+ * <literal>MultiType</literal> is passed as an argument to
+ * {@link Type#isAssignableFrom(Type)}, as well as removing non-intersecting
+ * types during a merge.
+ *
+ * Note: Currently the <litera>MultiType</literal> instance is reused as much
+ * as possible so that updates are visible from all frames. In addition, all
+ * <literal>MultiType</literal> merge paths are also updated. This is somewhat
+ * hackish, but it appears to handle most scenarios.
+ *
+ * @author Jason T. Greene
+ */
+
+/* TODO - A better, but more involved, approach would be to track the instruction
+ * offset that resulted in the creation of this type, and
+ * whenever the typeset changes, to force a merge on that position. This
+ * would require creating a new MultiType instance every time the typeset
+ * changes, and somehow communicating assignment changes to the Analyzer
+ */
+public class MultiType extends Type {
+    private Map interfaces;
+    private Type resolved;
+    private Type potentialClass;
+    private MultiType mergeSource;
+    private boolean changed = false;
+
+    public MultiType(Map interfaces) {
+        this(interfaces, null);
+    }
+
+    public MultiType(Map interfaces, Type potentialClass) {
+        super(null);
+        this.interfaces = interfaces;
+        this.potentialClass = potentialClass;
+    }
+
+    /**
+     * Gets the class that corresponds with this type. If this information
+     * is not yet known, java.lang.Object will be returned.
+     */
+    public CtClass getCtClass() {
+        if (resolved != null)
+            return resolved.getCtClass();
+
+        return Type.OBJECT.getCtClass();
+    }
+
+    /**
+     * Always returns null since this type is never used for an array.
+     */
+    public Type getComponent() {
+        return null;
+    }
+
+    /**
+     * Always returns 1, since this type is a reference.
+     */
+    public int getSize() {
+        return 1;
+    }
+
+    /**
+     * Always reutnrs false since this type is never used for an array
+     */
+    public boolean isArray() {
+        return false;
+    }
+
+    /**
+     * Returns true if the internal state has changed.
+     */
+    boolean popChanged() {
+        boolean changed = this.changed;
+        this.changed = false;
+        return changed;
+    }
+
+    public boolean isAssignableFrom(Type type) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public boolean isAssignableTo(Type type) {
+        if (resolved != null)
+            return type.isAssignableFrom(resolved);
+
+        if (Type.OBJECT.equals(type))
+            return true;
+
+        if (potentialClass != null && !type.isAssignableFrom(potentialClass))
+            potentialClass = null;
+
+        Map map = mergeMultiAndSingle(this, type);
+
+        if (map.size() == 1 && potentialClass == null) {
+            // Update previous merge paths to the same resolved type
+            resolved = Type.get((CtClass)map.values().iterator().next());
+            propogateResolved();
+
+            return true;
+        }
+
+        // Keep all previous merge paths up to date
+        if (map.size() >= 1) {
+            interfaces = map;
+            propogateState();
+
+            return true;
+        }
+
+        if (potentialClass != null) {
+            resolved = potentialClass;
+            propogateResolved();
+
+            return true;
+        }
+
+        return false;
+    }
+
+    private void propogateState() {
+        MultiType source = mergeSource;
+        while (source != null) {
+            source.interfaces = interfaces;
+            source.potentialClass = potentialClass;
+            source = source.mergeSource;
+        }
+    }
+
+    private void propogateResolved() {
+        MultiType source = mergeSource;
+        while (source != null) {
+            source.resolved = resolved;
+            source = source.mergeSource;
+        }
+    }
+
+    /**
+     * Always returns true, since this type is always a reference.
+     *
+     * @return true
+     */
+    public boolean isReference() {
+       return true;
+    }
+
+    private Map getAllMultiInterfaces(MultiType type) {
+        Map map = new HashMap();
+
+        Iterator iter = type.interfaces.values().iterator();
+        while (iter.hasNext()) {
+            CtClass intf = (CtClass)iter.next();
+            map.put(intf.getName(), intf);
+            getAllInterfaces(intf, map);
+        }
+
+        return map;
+    }
+
+
+    private Map mergeMultiInterfaces(MultiType type1, MultiType type2) {
+        Map map1 = getAllMultiInterfaces(type1);
+        Map map2 = getAllMultiInterfaces(type2);
+
+        return findCommonInterfaces(map1, map2);
+    }
+
+    private Map mergeMultiAndSingle(MultiType multi, Type single) {
+        Map map1 = getAllMultiInterfaces(multi);
+        Map map2 = getAllInterfaces(single.getCtClass(), null);
+
+        return findCommonInterfaces(map1, map2);
+    }
+
+    private boolean inMergeSource(MultiType source) {
+        while (source != null) {
+            if (source == this) {
+                System.out.println("INMERGESOURCE!");
+                return true;
+
+            }
+            source = source.mergeSource;
+        }
+
+        return false;
+    }
+
+    public Type merge(Type type) {
+        if (this == type)
+            return this;
+
+        if (type == UNINIT)
+            return this;
+
+        if (type == BOGUS)
+            return BOGUS;
+
+        if (type == null)
+            return this;
+
+        if (resolved != null)
+            return resolved.merge(type);
+
+        if (potentialClass != null) {
+            Type mergePotential = potentialClass.merge(type);
+            if (! mergePotential.equals(potentialClass) || mergePotential.popChanged()) {
+                potentialClass = Type.OBJECT.equals(mergePotential) ? null : mergePotential;
+                changed = true;
+            }
+        }
+
+        Map merged;
+
+        if (type instanceof MultiType) {
+            MultiType multi = (MultiType)type;
+
+            if (multi.resolved != null) {
+                merged = mergeMultiAndSingle(this, multi.resolved);
+            } else {
+                merged = mergeMultiInterfaces(multi, this);
+                if (! inMergeSource(multi))
+                    mergeSource = multi;
+            }
+        } else {
+            merged = mergeMultiAndSingle(this, type);
+        }
+
+        // Keep all previous merge paths up to date
+        if (merged.size() > 1 || (merged.size() == 1 && potentialClass != null)) {
+            // Check for changes
+            if (merged.size() != interfaces.size()) {
+                changed = true;
+            } else if (changed == false){
+                Iterator iter = merged.keySet().iterator();
+                while (iter.hasNext())
+                    if (! interfaces.containsKey(iter.next()))
+                        changed = true;
+            }
+
+            interfaces = merged;
+            propogateState();
+
+            return this;
+        }
+
+        if (merged.size() == 1) {
+            resolved = Type.get((CtClass) merged.values().iterator().next());
+        } else if (potentialClass != null){
+            resolved = potentialClass;
+        } else {
+            resolved = OBJECT;
+        }
+
+        propogateResolved();
+
+        return resolved;
+    }
+
+    public boolean equals(Object o) {
+        if (! (o instanceof MultiType))
+            return false;
+
+        MultiType multi = (MultiType) o;
+        if (resolved != null)
+            return resolved.equals(multi.resolved);
+        else if (multi.resolved != null)
+            return false;
+
+        return interfaces.keySet().equals(multi.interfaces.keySet());
+    }
+
+    public String toString() {
+        if (resolved != null)
+            return resolved.toString();
+
+        StringBuffer buffer = new StringBuffer("{");
+        Iterator iter = interfaces.keySet().iterator();
+        while (iter.hasNext()) {
+            buffer.append(iter.next());
+            buffer.append(", ");
+        }
+        buffer.setLength(buffer.length() - 2);
+        if (potentialClass != null)
+            buffer.append(", *").append(potentialClass.toString());
+        buffer.append("}");
+        return buffer.toString();
+    }
+}
diff --git a/src/main/javassist/bytecode/analysis/Subroutine.java b/src/main/javassist/bytecode/analysis/Subroutine.java
new file mode 100644 (file)
index 0000000..924d37a
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License.  Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Represents a nested method subroutine (marked by JSR and RET).
+ *
+ * @author Jason T. Greene
+ */
+public class Subroutine {
+    //private Set callers = new HashSet();
+    private List callers = new ArrayList();
+    private Set access = new HashSet();
+    private int start;
+
+    public Subroutine(int start, int caller) {
+        this.start = start;
+        callers.add(Integer.valueOf(caller));
+    }
+
+    public void addCaller(int caller) {
+        callers.add(Integer.valueOf(caller));
+    }
+
+    public int start() {
+        return start;
+    }
+
+    public void access(int index) {
+        access.add(Integer.valueOf(index));
+    }
+
+    public boolean isAccessed(int index) {
+        return access.contains(Integer.valueOf(index));
+    }
+
+    public Collection accessed() {
+        return access;
+    }
+
+    public Collection callers() {
+        return callers;
+    }
+
+    public String toString() {
+        return "start = " + start + " callers = " + callers.toString();
+    }
+}
\ No newline at end of file
diff --git a/src/main/javassist/bytecode/analysis/SubroutineScanner.java b/src/main/javassist/bytecode/analysis/SubroutineScanner.java
new file mode 100644 (file)
index 0000000..3feae53
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License.  Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javassist.bytecode.BadBytecode;
+import javassist.bytecode.CodeAttribute;
+import javassist.bytecode.CodeIterator;
+import javassist.bytecode.ExceptionTable;
+import javassist.bytecode.MethodInfo;
+import javassist.bytecode.Opcode;
+
+/**
+ * Discovers the subroutines in a method, and tracks all callers.
+ *
+ * @author Jason T. Greene
+ */
+public class SubroutineScanner implements Opcode {
+
+    private Subroutine[] subroutines;
+    Map subTable = new HashMap();
+    Set done = new HashSet();
+
+
+    public Subroutine[] scan(MethodInfo method) throws BadBytecode {
+        CodeAttribute code = method.getCodeAttribute();
+        CodeIterator iter = code.iterator();
+
+        subroutines = new Subroutine[code.getCodeLength()];
+        subTable.clear();
+        done.clear();
+
+        scan(0, iter, null);
+
+        ExceptionTable exceptions = code.getExceptionTable();
+        for (int i = 0; i < exceptions.size(); i++) {
+            int handler = exceptions.handlerPc(i);
+            // If an exception is thrown in subroutine, the handler
+            // is part of the same subroutine.
+            scan(handler, iter, subroutines[exceptions.startPc(i)]);
+        }
+
+        return subroutines;
+    }
+
+    private void scan(int pos, CodeIterator iter, Subroutine sub) throws BadBytecode {
+        // Skip already processed blocks
+        if (done.contains(Integer.valueOf(pos)))
+            return;
+
+        done.add(Integer.valueOf(pos));
+
+        int old = iter.lookAhead();
+        iter.move(pos);
+
+        boolean next;
+        do {
+            pos = iter.next();
+            next = scanOp(pos, iter, sub) && iter.hasNext();
+        } while (next);
+
+        iter.move(old);
+    }
+
+    private boolean scanOp(int pos, CodeIterator iter, Subroutine sub) throws BadBytecode {
+        subroutines[pos] = sub;
+
+        int opcode = iter.byteAt(pos);
+
+        if (opcode == TABLESWITCH) {
+            scanTableSwitch(pos, iter, sub);
+
+            return false;
+        }
+
+        if (opcode == LOOKUPSWITCH) {
+            scanLookupSwitch(pos, iter, sub);
+
+            return false;
+        }
+
+        // All forms of return and throw end current code flow
+        if (Util.isReturn(opcode) || opcode == RET || opcode == ATHROW)
+            return false;
+
+        if (Util.isJumpInstruction(opcode)) {
+            int target = Util.getJumpTarget(pos, iter);
+            if (opcode == JSR || opcode == JSR_W) {
+                Subroutine s = (Subroutine) subTable.get(Integer.valueOf(target));
+                if (s == null) {
+                    s = new Subroutine(target, pos);
+                    subTable.put(Integer.valueOf(target), s);
+                    scan(target, iter, s);
+                } else {
+                    s.addCaller(pos);
+                }
+            } else {
+                scan(target, iter, sub);
+
+                // GOTO ends current code flow
+                if (Util.isGoto(opcode))
+                    return false;
+            }
+        }
+
+        return true;
+    }
+
+    private void scanLookupSwitch(int pos, CodeIterator iter, Subroutine sub) throws BadBytecode {
+        int index = (pos & ~3) + 4;
+        // default
+        scan(pos + iter.s32bitAt(index), iter, sub);
+        int npairs = iter.s32bitAt(index += 4);
+        int end = npairs * 8 + (index += 4);
+
+        // skip "match"
+        for (index += 4; index < end; index += 8) {
+            int target = iter.s32bitAt(index) + pos;
+            scan(target, iter, sub);
+        }
+    }
+
+    private void scanTableSwitch(int pos, CodeIterator iter, Subroutine sub) throws BadBytecode {
+        // Skip 4 byte alignment padding
+        int index = (pos & ~3) + 4;
+        // default
+        scan(pos + iter.s32bitAt(index), iter, sub);
+        int low = iter.s32bitAt(index += 4);
+        int high = iter.s32bitAt(index += 4);
+        int end = (high - low + 1) * 4 + (index += 4);
+
+        // Offset table
+        for (; index < end; index += 4) {
+            int target = iter.s32bitAt(index) + pos;
+            scan(target, iter, sub);
+        }
+    }
+
+
+}
diff --git a/src/main/javassist/bytecode/analysis/Type.java b/src/main/javassist/bytecode/analysis/Type.java
new file mode 100644 (file)
index 0000000..38df93f
--- /dev/null
@@ -0,0 +1,592 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License.  Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.NotFoundException;
+
+/**
+ * Represents a JVM type in data-flow analysis. This abstraction is necessary since
+ * a JVM type not only includes all normal Java types, but also a few special types
+ * that are used by the JVM internally. See the static field types on this class for
+ * more info on these special types.
+ *
+ * All primitive and special types reuse the same instance, so identity comparison can
+ * be used when examining them. Normal java types must use {@link #equals(Object)} to
+ * compare type instances.
+ *
+ * In most cases, applications which consume this API, only need to call {@link #getCtClass()}
+ * to obtain the needed type information.
+ *
+ * @author Jason T. Greene
+ */
+public class Type {
+    private final CtClass clazz;
+    private final boolean special;
+
+    private static final Map prims = new IdentityHashMap();
+    /** Represents the double primitive type */
+    public static final Type DOUBLE = new Type(CtClass.doubleType);
+    /** Represents the boolean primitive type */
+    public static final Type BOOLEAN = new Type(CtClass.booleanType);
+    /** Represents the long primitive type */
+    public static final Type LONG = new Type(CtClass.longType);
+    /** Represents the char primitive type */
+    public static final Type CHAR = new Type(CtClass.charType);
+    /** Represents the byte primitive type */
+    public static final Type BYTE = new Type(CtClass.byteType);
+    /** Represents the short primitive type */
+    public static final Type SHORT = new Type(CtClass.shortType);
+    /** Represents the integer primitive type */
+    public static final Type INTEGER = new Type(CtClass.intType);
+    /** Represents the float primitive type */
+    public static final Type FLOAT = new Type(CtClass.floatType);
+    /** Represents the void primitive type */
+    public static final Type VOID = new Type(CtClass.voidType);
+
+    /**
+     * Represents an unknown, or null type. This occurs when aconst_null is used.
+     * It is important not to treat this type as java.lang.Object, since a null can
+     * be assigned to any reference type. The analyzer will replace these with
+     * an actual known type if it can be determined by a merged path with known type
+     * information. If this type is encountered on a frame then it is guaranteed to
+     * be null, and the type information is simply not available. Any attempts to
+     * infer the type, without further information from the compiler would be a guess.
+     */
+    public static final Type UNINIT = new Type(null);
+
+    /**
+     * Represents an internal JVM return address, which is used by the RET
+     * instruction to return to a JSR that invoked the subroutine.
+     */
+    public static final Type RETURN_ADDRESS = new Type(null, true);
+
+    /** A placeholder used by the analyzer for the second word position of a double-word type */
+    public static final Type TOP = new Type(null, true);
+
+    /**
+     * Represents a non-accessible value. Code cannot access the value this type
+     * represents. It occurs when bytecode reuses a local variable table
+     * position with non-mergable types. An example would be compiled code which
+     * uses the same position for a primitive type in one branch, and a reference type
+     * in another branch.
+     */
+    public static final Type BOGUS = new Type(null, true);
+
+    /** Represents the java.lang.Object reference type */
+    public static final Type OBJECT = lookupType("java.lang.Object");
+    /** Represents the java.io.Serializable reference type */
+    public static final Type SERIALIZABLE = lookupType("java.io.Serializable");
+    /** Represents the java.lang.Coneable reference type */
+    public static final Type CLONEABLE = lookupType("java.lang.Cloneable");
+    /** Represents the java.lang.Throwable reference type */
+    public static final Type THROWABLE = lookupType("java.lang.Throwable");
+
+    static {
+        prims.put(CtClass.doubleType, DOUBLE);
+        prims.put(CtClass.longType, LONG);
+        prims.put(CtClass.charType, CHAR);
+        prims.put(CtClass.shortType, SHORT);
+        prims.put(CtClass.intType, INTEGER);
+        prims.put(CtClass.floatType, FLOAT);
+        prims.put(CtClass.byteType, BYTE);
+        prims.put(CtClass.booleanType, BOOLEAN);
+        prims.put(CtClass.voidType, VOID);
+
+    }
+
+    /**
+     * Obtain the Type for a given class. If the class is a primitive,
+     * the the unique type instance for the primitive will be returned.
+     * Otherwise a new Type instance representing the class is returned.
+     *
+     * @param clazz The java class
+     * @return a type instance for this class
+     */
+    public static Type get(CtClass clazz) {
+        Type type = (Type)prims.get(clazz);
+        return type != null ? type : new Type(clazz);
+    }
+
+    private static Type lookupType(String name) {
+        try {
+             return new Type(ClassPool.getDefault().get(name));
+        } catch (NotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    Type(CtClass clazz) {
+        this(clazz, false);
+    }
+
+    private Type(CtClass clazz, boolean special) {
+        this.clazz = clazz;
+        this.special = special;
+    }
+
+    // Used to indicate a merge internally triggered a change
+    boolean popChanged() {
+        return false;
+    }
+
+    /**
+     * Gets the word size of this type. Double-word types, such as long and double
+     * will occupy two positions on the local variable table or stack.
+     *
+     * @return the number of words needed to hold this type
+     */
+    public int getSize() {
+        return clazz == CtClass.doubleType || clazz == CtClass.longType || this == TOP ? 2 : 1;
+    }
+
+    /**
+     * Returns the class this type represents. If the type is special, null will be returned.
+     *
+     * @return the class for this type, or null if special
+     */
+    public CtClass getCtClass() {
+        return clazz;
+    }
+
+    /**
+     * Returns whether or not this type is a normal java reference, i.e. it is or extends java.lang.Object.
+     *
+     * @return true if a java reference, false if a primitive or special
+     */
+    public boolean isReference() {
+        return !special && (clazz == null || !clazz.isPrimitive());
+    }
+
+    /**
+     * Returns whether or not the type is special. A special type is one that is either used
+     * for internal tracking, or is only used internally by the JVM.
+     *
+     * @return true if special, false if not
+     */
+    public boolean isSpecial() {
+        return special;
+    }
+
+    /**
+     * Returns whether or not this type is an array.
+     *
+     * @return true if an array, false if not
+     */
+    public boolean isArray() {
+        return clazz != null && clazz.isArray();
+    }
+
+    /**
+     * Returns the number of dimensions of this array. If the type is not an
+     * array zero is returned.
+     *
+     * @return zero if not an array, otherwise the number of array dimensions.
+     */
+    public int getDimensions() {
+        if (!isArray()) return 0;
+
+        String name = clazz.getName();
+        int pos = name.length() - 2;
+        int count = 0;
+        while (name.charAt(pos) == '[' ) {
+            pos -= 2;
+            count++;
+        }
+
+        return count;
+    }
+
+    /**
+     * Returns the array component if this type is an array. If the type
+     * is not an array null is returned.
+     *
+     * @return the array component if an array, otherwise null
+     */
+    public Type getComponent() {
+        if (this.clazz == null || !this.clazz.isArray())
+            return null;
+
+        CtClass component;
+        try {
+            component = this.clazz.getComponentType();
+        } catch (NotFoundException e) {
+            throw new RuntimeException(e);
+        }
+
+        Type type = (Type)prims.get(component);
+        return (type != null) ? type : new Type(component);
+    }
+
+    /**
+     * Determines whether this type is assignable, to the passed type.
+     * A type is assignable to another if it is either the same type, or
+     * a sub-type.
+     *
+     * @param type the type to test assignability to
+     * @return true if this is assignable to type, otherwise false
+     */
+    public boolean isAssignableFrom(Type type) {
+        if (this == type)
+            return true;
+
+        if ((type == UNINIT && isReference()) || this == UNINIT && type.isReference())
+            return true;
+
+        if (type instanceof MultiType)
+            return ((MultiType)type).isAssignableTo(this);
+
+        if (type instanceof MultiArrayType)
+            return ((MultiArrayType)type).isAssignableTo(this);
+
+
+        // Primitives and Special types must be identical
+        if (clazz == null || clazz.isPrimitive())
+            return false;
+
+        try {
+            return type.clazz.subtypeOf(clazz);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Finds the common base type, or interface which both this and the specified
+     * type can be assigned. If there is more than one possible answer, then a {@link MultiType},
+     * or a {@link MultiArrayType} is returned. Multi-types have special rules,
+     * and successive merges and assignment tests on them will alter their internal state,
+     * as well as other multi-types they have been merged with. This method is used by
+     * the data-flow analyzer to merge the type state from multiple branches.
+     *
+     * @param type the type to merge with
+     * @return the merged type
+     */
+    public Type merge(Type type) {
+        if (type == this)
+            return this;
+        if (type == null)
+            return this;
+        if (type == Type.UNINIT)
+            return this;
+        if (this == Type.UNINIT)
+            return type;
+
+        // Unequal primitives and special types can not be merged
+        if (! type.isReference() || ! this.isReference())
+            return BOGUS;
+
+        // Centralize merging of multi-interface types
+        if (type instanceof MultiType)
+            return type.merge(this);
+
+        if (type.isArray() && this.isArray())
+            return mergeArray(type);
+
+        try {
+            return mergeClasses(type);
+        } catch (NotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+   Type getRootComponent(Type type) {
+        while (type.isArray())
+            type = type.getComponent();
+
+        return type;
+    }
+
+    private Type createArray(Type rootComponent, int dims) {
+        if (rootComponent instanceof MultiType)
+            return new MultiArrayType((MultiType) rootComponent, dims);
+
+        String name = arrayName(rootComponent.clazz.getName(), dims);
+
+        Type type;
+        try {
+            type = Type.get(getClassPool(rootComponent).get(name));
+        } catch (NotFoundException e) {
+            throw new RuntimeException(e);
+        }
+
+        return type;
+    }
+
+    String arrayName(String component, int dims) {
+     // Using char[] since we have no StringBuilder in JDK4, and StringBuffer is slow.
+        // Although, this is more efficient even if we did have one.
+        int i = component.length();
+        int size = i + dims * 2;
+        char[] string = new char[size];
+        component.getChars(0, i, string, 0);
+        while (i < size) {
+            string[i++] = '[';
+            string[i++] = ']';
+        }
+        component = new String(string);
+        return component;
+    }
+
+    private ClassPool getClassPool(Type rootComponent) {
+        ClassPool pool = rootComponent.clazz.getClassPool();
+        return pool != null ? pool : ClassPool.getDefault();
+    }
+
+    private Type mergeArray(Type type) {
+        Type typeRoot = getRootComponent(type);
+        Type thisRoot = getRootComponent(this);
+        int typeDims = type.getDimensions();
+        int thisDims = this.getDimensions();
+
+        // Array commponents can be merged when the dimensions are equal
+        if (typeDims == thisDims) {
+            Type mergedComponent = thisRoot.merge(typeRoot);
+
+            // If the components can not be merged (a primitive component mixed with a different type)
+            // then Object is the common type.
+            if (mergedComponent == Type.BOGUS)
+                return Type.OBJECT;
+
+            return createArray(mergedComponent, thisDims);
+        }
+
+        Type targetRoot;
+        int targetDims;
+
+        if (typeDims < thisDims) {
+            targetRoot = typeRoot;
+            targetDims = typeDims;
+        } else {
+            targetRoot = thisRoot;
+            targetDims = thisDims;
+        }
+
+        // Special case, arrays are cloneable and serializable, so prefer them when dimensions differ
+        if (eq(CLONEABLE.clazz, targetRoot.clazz) || eq(SERIALIZABLE.clazz, targetRoot.clazz))
+            return createArray(targetRoot, targetDims);
+
+        return createArray(OBJECT, targetDims);
+    }
+
+    private static CtClass findCommonSuperClass(CtClass one, CtClass two) throws NotFoundException {
+        CtClass deep = one;
+        CtClass shallow = two;
+        CtClass backupShallow = shallow;
+        CtClass backupDeep = deep;
+
+        // Phase 1 - Find the deepest hierarchy, set deep and shallow correctly
+        for (;;) {
+            // In case we get lucky, and find a match early
+            if (eq(deep, shallow) && deep.getSuperclass() != null)
+                return deep;
+
+            CtClass deepSuper = deep.getSuperclass();
+            CtClass shallowSuper = shallow.getSuperclass();
+
+            if (shallowSuper == null) {
+                // right, now reset shallow
+                shallow = backupShallow;
+                break;
+            }
+
+            if (deepSuper == null) {
+                // wrong, swap them, since deep is now useless, its our tmp before we swap it
+                deep = backupDeep;
+                backupDeep = backupShallow;
+                backupShallow = deep;
+
+                deep = shallow;
+                shallow = backupShallow;
+                break;
+            }
+
+            deep = deepSuper;
+            shallow = shallowSuper;
+        }
+
+        // Phase 2 - Move deepBackup up by (deep end - deep)
+        for (;;) {
+            deep = deep.getSuperclass();
+            if (deep == null)
+                break;
+
+            backupDeep = backupDeep.getSuperclass();
+        }
+
+        deep = backupDeep;
+
+        // Phase 3 - The hierarchy positions are now aligned
+        // The common super class is easy to find now
+        while (!eq(deep, shallow)) {
+            deep = deep.getSuperclass();
+            shallow = shallow.getSuperclass();
+        }
+
+        return deep;
+    }
+
+    private Type mergeClasses(Type type) throws NotFoundException {
+        CtClass superClass = findCommonSuperClass(this.clazz, type.clazz);
+
+        // If its Object, then try and find a common interface(s)
+        if (superClass.getSuperclass() == null) {
+            Map interfaces = findCommonInterfaces(type);
+            if (interfaces.size() == 1)
+                return new Type((CtClass) interfaces.values().iterator().next());
+            if (interfaces.size() > 1)
+                return new MultiType(interfaces);
+
+            // Only Object is in common
+            return new Type(superClass);
+        }
+
+        // Check for a common interface that is not on the found supertype
+        Map commonDeclared = findExclusiveDeclaredInterfaces(type, superClass);
+        if (commonDeclared.size() > 0) {
+            return new MultiType(commonDeclared, new Type(superClass));
+        }
+
+        return new Type(superClass);
+    }
+
+    private Map findCommonInterfaces(Type type) {
+        Map typeMap = getAllInterfaces(type.clazz, null);
+        Map thisMap = getAllInterfaces(this.clazz, null);
+
+        return findCommonInterfaces(typeMap, thisMap);
+    }
+
+    private Map findExclusiveDeclaredInterfaces(Type type, CtClass exclude) {
+        Map typeMap = getDeclaredInterfaces(type.clazz, null);
+        Map thisMap = getDeclaredInterfaces(this.clazz, null);
+        Map excludeMap = getAllInterfaces(exclude, null);
+
+        Iterator i = excludeMap.keySet().iterator();
+        while (i.hasNext()) {
+            Object intf = i.next();
+            typeMap.remove(intf);
+            thisMap.remove(intf);
+        }
+
+        return findCommonInterfaces(typeMap, thisMap);
+    }
+
+
+    Map findCommonInterfaces(Map typeMap, Map alterMap) {
+        Iterator i = alterMap.keySet().iterator();
+        while (i.hasNext()) {
+            if (! typeMap.containsKey(i.next()))
+                i.remove();
+        }
+
+        // Reduce to subinterfaces
+        // This does not need to be recursive since we make a copy,
+        // and that copy contains all super types for the whole hierarchy
+        i = new ArrayList(alterMap.values()).iterator();
+        while (i.hasNext()) {
+            CtClass intf = (CtClass) i.next();
+            CtClass[] interfaces;
+            try {
+                interfaces = intf.getInterfaces();
+            } catch (NotFoundException e) {
+                throw new RuntimeException(e);
+            }
+
+            for (int c = 0; c < interfaces.length; c++)
+                alterMap.remove(interfaces[c].getName());
+        }
+
+        return alterMap;
+    }
+
+    Map getAllInterfaces(CtClass clazz, Map map) {
+        if (map == null)
+            map = new HashMap();
+
+        if (clazz.isInterface())
+            map.put(clazz.getName(), clazz);
+        do {
+            try {
+                CtClass[] interfaces = clazz.getInterfaces();
+                for (int i = 0; i < interfaces.length; i++) {
+                    CtClass intf = interfaces[i];
+                    map.put(intf.getName(), intf);
+                    getAllInterfaces(intf, map);
+                }
+
+                clazz = clazz.getSuperclass();
+            } catch (NotFoundException e) {
+                throw new RuntimeException(e);
+            }
+        } while (clazz != null);
+
+        return map;
+    }
+
+    Map getDeclaredInterfaces(CtClass clazz, Map map) {
+        if (map == null)
+            map = new HashMap();
+
+        if (clazz.isInterface())
+            map.put(clazz.getName(), clazz);
+
+        CtClass[] interfaces;
+        try {
+            interfaces = clazz.getInterfaces();
+        } catch (NotFoundException e) {
+            throw new RuntimeException(e);
+        }
+
+        for (int i = 0; i < interfaces.length; i++) {
+            CtClass intf = interfaces[i];
+            map.put(intf.getName(), intf);
+            getDeclaredInterfaces(intf, map);
+        }
+
+        return map;
+    }
+
+    public boolean equals(Object o) {
+        if (! (o instanceof Type))
+            return false;
+
+        return o.getClass() == getClass() && eq(clazz, ((Type)o).clazz);
+    }
+
+    static boolean eq(CtClass one, CtClass two) {
+        return one == two || (one != null && two != null && one.getName().equals(two.getName()));
+    }
+
+    public String toString() {
+        if (this == BOGUS)
+            return "BOGUS";
+        if (this == UNINIT)
+            return "UNINIT";
+        if (this == RETURN_ADDRESS)
+            return "RETURN ADDRESS";
+        if (this == TOP)
+            return "TOP";
+
+        return clazz == null ? "null" : clazz.getName();
+    }
+}
diff --git a/src/main/javassist/bytecode/analysis/Util.java b/src/main/javassist/bytecode/analysis/Util.java
new file mode 100644 (file)
index 0000000..a8cdfcc
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License.  Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.bytecode.analysis;
+
+import javassist.bytecode.CodeIterator;
+import javassist.bytecode.Opcode;
+
+/**
+ * A set of common utility methods.
+ *
+ * @author Jason T. Greene
+ */
+public class Util implements Opcode {
+    public static int getJumpTarget(int pos, CodeIterator iter) {
+        int opcode = iter.byteAt(pos);
+        pos += (opcode == JSR_W || opcode == GOTO_W) ? iter.s32bitAt(pos + 1) : iter.s16bitAt(pos + 1);
+        return pos;
+    }
+
+    public static boolean isJumpInstruction(int opcode) {
+        return (opcode >= IFEQ && opcode <= JSR) || opcode == IFNULL || opcode == IFNONNULL || opcode == JSR_W || opcode == GOTO_W;
+    }
+
+    public static boolean isGoto(int opcode) {
+        return opcode == GOTO || opcode == GOTO_W;
+    }
+
+    public static boolean isJsr(int opcode) {
+        return opcode == JSR || opcode == JSR_W;
+    }
+
+    public static boolean isReturn(int opcode) {
+        return (opcode >= IRETURN && opcode <= RETURN);
+    }
+}
diff --git a/src/main/javassist/bytecode/analysis/package.html b/src/main/javassist/bytecode/analysis/package.html
new file mode 100644 (file)
index 0000000..b141670
--- /dev/null
@@ -0,0 +1,19 @@
+<html>
+<body>
+Bytecode Analysis API.
+
+<p>This package provides an API for performing data-flow analysis on a method's bytecode.
+This allows the user to determine the type state of the stack and local variable table 
+at the start of every instruction. In addition this API can be used to validate 
+bytecode, find dead bytecode, and identify unnecessary checkcasts.
+
+<p>The users of this package must know the specifications of
+class file and Java bytecode.  For more details, read this book:
+
+<ul>Tim Lindholm and Frank Yellin,
+"The Java Virtual Machine Specification 2nd Ed.",
+Addison-Wesley, 1999.
+</ul>
+
+</body>
+</html>
index d3036056fc5d882a2a48c2f867cdf17d1f2db106..1af424ed9c4f7415992b09d3e79075b36dd08084 100644 (file)
  */
 package javassist.convert;
 
+import javassist.CannotCompileException;
 import javassist.CtClass;
 import javassist.NotFoundException;
 import javassist.CodeConverter.ArrayAccessReplacementMethodNames;
 import javassist.bytecode.BadBytecode;
 import javassist.bytecode.CodeIterator;
 import javassist.bytecode.ConstPool;
+import javassist.bytecode.MethodInfo;
+import javassist.bytecode.analysis.Analyzer;
+import javassist.bytecode.analysis.Frame;
 
 /**
- *  
+ * A transformer which replaces array access with static method invocations.
+ * 
  * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
- * @version $Revision: 1.4 $ 
+ * @author Jason T. Greene
+ * @version $Revision: 1.5 $
  */
-public class TransformAccessArrayField extends Transformer {
-// CtClass componentType;
-
-   String methodClassname;
-   ArrayAccessReplacementMethodNames names;
-
-   public TransformAccessArrayField(Transformer next, String methodClassname,
-                                    ArrayAccessReplacementMethodNames names)
-       throws NotFoundException
-   {
-       super(next);
-       this.methodClassname = methodClassname;
-       this.names = names;
-   }
-
-   public int transform(CtClass tclazz, int pos, CodeIterator iterator,
-                        ConstPool cp) throws BadBytecode
-   {
-      int c = iterator.byteAt(pos);
-      
-      if (c == AALOAD || c == BALOAD || c == CALOAD || c == DALOAD
-          || c == FALOAD || c == IALOAD || c == LALOAD || c == SALOAD)
-         replace(cp, iterator, pos, c, getLoadReplacementSignature(c));
-      else if (c == AASTORE || c == BASTORE || c == CASTORE || c == DASTORE
-               || c == FASTORE || c == IASTORE || c == LASTORE || c == SASTORE)
-         replace(cp, iterator, pos, c, getStoreReplacementSignature(c));
-
-      return pos;
-   }
-   
-   private void replace(ConstPool cp, CodeIterator iterator,
-                        int pos, int opcode, String signature)
-       throws BadBytecode
-   {
-      String methodName = getMethodName(opcode);
-      if (methodName != null) {
-         iterator.insertGap(2);
-         int mi = cp.addClassInfo(methodClassname);
-         int methodref = cp.addMethodrefInfo(mi, methodName, signature);
-         iterator.writeByte(INVOKESTATIC, pos);
-         iterator.write16bit(methodref, pos + 1);
-      }
-   }
-
-   private String getMethodName(int opcode) {
-        String methodName = null;
-        switch (opcode) {
-        case AALOAD:
-            methodName = names.objectRead();
-            break;
-        case BALOAD:
-            methodName = names.byteOrBooleanRead();
-            break;
-        case CALOAD:
-            methodName = names.charRead();
-            break;
-        case DALOAD:
-            methodName = names.doubleRead();
-            break;
-        case FALOAD:
-            methodName = names.floatRead();
-            break;
-        case IALOAD:
-            methodName = names.intRead();
-            break;
-        case SALOAD:
-            methodName = names.shortRead();
-            break;
-        case LALOAD:
-            methodName = names.longRead();
-            break;
-        case AASTORE:
-            methodName = names.objectWrite();
-            break;
-        case BASTORE:
-            methodName = names.byteOrBooleanWrite();
-            break;
-        case CASTORE:
-            methodName = names.charWrite();
-            break;
-        case DASTORE:
-            methodName = names.doubleWrite();
-            break;
-        case FASTORE:
-            methodName = names.floatWrite();
-            break;
-        case IASTORE:
-            methodName = names.intWrite();
-            break;
-        case SASTORE:
-            methodName = names.shortWrite();
-            break;
-        case LASTORE:
-            methodName = names.longWrite();
-            break;
-        }
+public final class TransformAccessArrayField extends Transformer {
+       private final String methodClassname;
+       private final ArrayAccessReplacementMethodNames names;
+       private Frame[] frames;
+       private int offset;
 
-        if (methodName.equals(""))
-            methodName = null;
+       public TransformAccessArrayField(Transformer next, String methodClassname,
+                       ArrayAccessReplacementMethodNames names) throws NotFoundException {
+               super(next);
+               this.methodClassname = methodClassname;
+               this.names = names;
 
-        return methodName;
-    }
+       }
 
-   private String getLoadReplacementSignature(int opcode)
-       throws BadBytecode
-   {
-        switch (opcode) {
-        case AALOAD:
-            return "(Ljava/lang/Object;I)Ljava/lang/Object;";
-        case BALOAD:
-            return "(Ljava/lang/Object;I)B";
-        case CALOAD:
-            return "(Ljava/lang/Object;I)C";
-        case DALOAD:
-            return "(Ljava/lang/Object;I)D";
-        case FALOAD:
-            return "(Ljava/lang/Object;I)F";
-        case IALOAD:
-            return "(Ljava/lang/Object;I)I";
-        case SALOAD:
-            return "(Ljava/lang/Object;I)S";
-        case LALOAD:
-            return "(Ljava/lang/Object;I)J";
-        }
+       public void initialize(ConstPool cp, CtClass clazz, MethodInfo minfo) throws CannotCompileException {
+           /*
+         * This transformer must be isolated from other transformers, since some
+         * of them affect the local variable and stack maximums without updating
+         * the code attribute to reflect the changes. This screws up the
+         * data-flow analyzer, since it relies on consistent code state. Even
+         * if the attribute values were updated correctly, we would have to
+         * detect it, and redo analysis, which is not cheap. Instead, we are
+         * better off doing all changes in initialize() before everyone else has
+         * a chance to muck things up.
+         */ 
+        CodeIterator iterator = minfo.getCodeAttribute().iterator();
+        while (iterator.hasNext()) {
+            try {
+                int pos = iterator.next();
+                int c = iterator.byteAt(pos);
 
-        throw new BadBytecode(opcode);
-    }
-   
-   private String getStoreReplacementSignature(int opcode)
-       throws BadBytecode
-   {
-        switch (opcode) {
-        case AASTORE:
-            return "(Ljava/lang/Object;ILjava/lang/Object;)V";
-        case BASTORE:
-            return "(Ljava/lang/Object;IB)V";
-        case CASTORE:
-            return "(Ljava/lang/Object;IC)V";
-        case DASTORE:
-            return "(Ljava/lang/Object;ID)V";
-        case FASTORE:
-            return "(Ljava/lang/Object;IF)V";
-        case IASTORE:
-            return "(Ljava/lang/Object;II)V";
-        case SASTORE:
-            return "(Ljava/lang/Object;IS)V";
-        case LASTORE:
-            return "(Ljava/lang/Object;IJ)V";
-        }
+                if (c == AALOAD)
+                    initFrames(clazz, minfo);
+
+                if (c == AALOAD || c == BALOAD || c == CALOAD || c == DALOAD
+                        || c == FALOAD || c == IALOAD || c == LALOAD
+                        || c == SALOAD) {
+                    pos = replace(cp, iterator, pos, c, getLoadReplacementSignature(c));
+                } else if (c == AASTORE || c == BASTORE || c == CASTORE
+                        || c == DASTORE || c == FASTORE || c == IASTORE
+                        || c == LASTORE || c == SASTORE) {
+                    pos = replace(cp, iterator, pos, c, getStoreReplacementSignature(c));
+                }
 
-        throw new BadBytecode(opcode);
+            } catch (Exception e) {
+                throw new CannotCompileException(e);
+            }
+        }
     }
+
+       public void clean() {
+               frames = null;
+               offset = -1;
+       }
+
+       public int transform(CtClass tclazz, int pos, CodeIterator iterator,
+                       ConstPool cp) throws BadBytecode {
+           // Do nothing, see above comment
+               return pos;
+       }
+
+       private Frame getFrame(int pos) throws BadBytecode {
+               return frames[pos - offset]; // Adjust pos
+       }
+
+       private void initFrames(CtClass clazz, MethodInfo minfo) throws BadBytecode {
+               if (frames == null) {
+                       frames = ((new Analyzer())).analyze(clazz, minfo);
+                       offset = 0; // start tracking changes
+               }
+       }
+
+       private int updatePos(int pos, int increment) {
+               if (offset > -1)
+                       offset += increment;
+
+               return pos + increment;
+       }
+
+       private String getTopType(int pos) throws BadBytecode {
+           Frame frame = getFrame(pos);
+           if (frame == null)
+               return null;
+           
+           CtClass clazz = frame.peek().getCtClass();
+           return clazz != null ? clazz.getName() : null;
+       }
+
+       private int replace(ConstPool cp, CodeIterator iterator, int pos,
+                       int opcode, String signature) throws BadBytecode {
+           String castType = null;
+               String methodName = getMethodName(opcode);
+               if (methodName != null) {
+                   // See if the object must be cast
+                   if (opcode == AALOAD) {
+                       castType = getTopType(iterator.lookAhead());
+                       // Do not replace an AALOAD instruction that we do not have a type for
+                       // This happens when the state is guaranteed to be null (Type.UNINIT)
+                       // So we don't really care about this case. 
+                       if (castType == null)
+                           return pos; 
+                       if ("java.lang.Object".equals(castType))
+                           castType = null;
+                   }
+
+                   // The gap may include extra padding
+                   int gapLength = iterator.insertGap(pos, castType != null ? 5 : 2);
+                       
+                       int mi = cp.addClassInfo(methodClassname);
+                       int methodref = cp.addMethodrefInfo(mi, methodName, signature);
+                       iterator.writeByte(INVOKESTATIC, pos);
+                       iterator.write16bit(methodref, pos + 1);
+                       
+                       if (castType != null) {
+                           int index = cp.addClassInfo(castType);
+                           iterator.writeByte(CHECKCAST, pos + 3);
+                           iterator.write16bit(index, pos + 4);
+                       }
+                       
+                       pos = updatePos(pos, gapLength);
+               }
+
+               return pos;
+       }
+
+       private String getMethodName(int opcode) {
+               String methodName = null;
+               switch (opcode) {
+               case AALOAD:
+                       methodName = names.objectRead();
+                       break;
+               case BALOAD:
+                       methodName = names.byteOrBooleanRead();
+                       break;
+               case CALOAD:
+                       methodName = names.charRead();
+                       break;
+               case DALOAD:
+                       methodName = names.doubleRead();
+                       break;
+               case FALOAD:
+                       methodName = names.floatRead();
+                       break;
+               case IALOAD:
+                       methodName = names.intRead();
+                       break;
+               case SALOAD:
+                       methodName = names.shortRead();
+                       break;
+               case LALOAD:
+                       methodName = names.longRead();
+                       break;
+               case AASTORE:
+                       methodName = names.objectWrite();
+                       break;
+               case BASTORE:
+                       methodName = names.byteOrBooleanWrite();
+                       break;
+               case CASTORE:
+                       methodName = names.charWrite();
+                       break;
+               case DASTORE:
+                       methodName = names.doubleWrite();
+                       break;
+               case FASTORE:
+                       methodName = names.floatWrite();
+                       break;
+               case IASTORE:
+                       methodName = names.intWrite();
+                       break;
+               case SASTORE:
+                       methodName = names.shortWrite();
+                       break;
+               case LASTORE:
+                       methodName = names.longWrite();
+                       break;
+               }
+
+               if (methodName.equals(""))
+                       methodName = null;
+
+               return methodName;
+       }
+
+       private String getLoadReplacementSignature(int opcode) throws BadBytecode {
+               switch (opcode) {
+               case AALOAD:
+                       return "(Ljava/lang/Object;I)Ljava/lang/Object;";
+               case BALOAD:
+                       return "(Ljava/lang/Object;I)B";
+               case CALOAD:
+                       return "(Ljava/lang/Object;I)C";
+               case DALOAD:
+                       return "(Ljava/lang/Object;I)D";
+               case FALOAD:
+                       return "(Ljava/lang/Object;I)F";
+               case IALOAD:
+                       return "(Ljava/lang/Object;I)I";
+               case SALOAD:
+                       return "(Ljava/lang/Object;I)S";
+               case LALOAD:
+                       return "(Ljava/lang/Object;I)J";
+               }
+
+               throw new BadBytecode(opcode);
+       }
+
+       private String getStoreReplacementSignature(int opcode) throws BadBytecode {
+               switch (opcode) {
+               case AASTORE:
+                       return "(Ljava/lang/Object;ILjava/lang/Object;)V";
+               case BASTORE:
+                       return "(Ljava/lang/Object;IB)V";
+               case CASTORE:
+                       return "(Ljava/lang/Object;IC)V";
+               case DASTORE:
+                       return "(Ljava/lang/Object;ID)V";
+               case FASTORE:
+                       return "(Ljava/lang/Object;IF)V";
+               case IASTORE:
+                       return "(Ljava/lang/Object;II)V";
+               case SASTORE:
+                       return "(Ljava/lang/Object;IS)V";
+               case LASTORE:
+                       return "(Ljava/lang/Object;IJ)V";
+               }
+
+               throw new BadBytecode(opcode);
+       }
 }
index d4ea0ecfe87c467d10b7dec5d5b10434cbd4805e..1095cf5b2c14285d8a5344085a2b2d340677ec62 100644 (file)
 
 package javassist.convert;
 
-import javassist.bytecode.*;
-import javassist.CtClass;
 import javassist.CannotCompileException;
+import javassist.CtClass;
+import javassist.bytecode.BadBytecode;
+import javassist.bytecode.CodeAttribute;
+import javassist.bytecode.CodeIterator;
+import javassist.bytecode.ConstPool;
+import javassist.bytecode.MethodInfo;
+import javassist.bytecode.Opcode;
 
 /**
  * Transformer and its subclasses are used for executing
@@ -35,6 +40,10 @@ public abstract class Transformer implements Opcode {
     public Transformer getNext() { return next; }
 
     public void initialize(ConstPool cp, CodeAttribute attr) {}
+    
+    public void initialize(ConstPool cp, CtClass clazz, MethodInfo minfo) throws CannotCompileException { 
+       initialize(cp, minfo.getCodeAttribute());
+    }
 
     public void clean() {}
 
diff --git a/src/main/javassist/tools/framedump.java b/src/main/javassist/tools/framedump.java
new file mode 100644 (file)
index 0000000..0a30fb1
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License.  Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+package javassist.tools;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.bytecode.analysis.FramePrinter;
+
+/**
+ * framedump is a tool for viewing a merged combination of the instructions and frame state
+ *  of all methods in a class.
+ *
+ * <p>For example,
+ * <ul><pre>% java javassist.tools.framedump foo.class</pre></ul>
+ */
+public class framedump {
+    private framedump() {}
+
+    /**
+     * Main method.
+     *
+     * @param args <code>args[0]</code> is the class file name.
+     */
+    public static void main(String[] args) throws Exception {
+        if (args.length != 1) {
+            System.err.println("Usage: java javassist.tools.framedump <class file name>");
+            return;
+        }
+        
+        ClassPool pool = ClassPool.getDefault();
+        CtClass clazz = pool.get(args[0]);
+        System.out.println("Frame Dump of " + clazz.getName() + ":");
+        FramePrinter.print(clazz, System.out);
+    }
+}
diff --git a/src/test/test/Test.java b/src/test/test/Test.java
deleted file mode 100644 (file)
index 6d34165..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-package test;
-
-import javassist.*;
-
-public class Test {
-    public static void main(String[] args) throws Exception {
-        CtClass ctClass = ClassPool.getDefault().get("JavassistTarget");
-        ctClass.getMethod("method", "(Ljava/lang/String;)V").insertAfter("");
-    }
-}
diff --git a/src/test/test/javassist/bytecode/analysis/AnalyzerTest.java b/src/test/test/javassist/bytecode/analysis/AnalyzerTest.java
new file mode 100644 (file)
index 0000000..0c5a77e
--- /dev/null
@@ -0,0 +1,411 @@
+package test.javassist.bytecode.analysis;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.CtMethod;
+import javassist.bytecode.AccessFlag;
+import javassist.bytecode.BadBytecode;
+import javassist.bytecode.Bytecode;
+import javassist.bytecode.CodeIterator;
+import javassist.bytecode.MethodInfo;
+import javassist.bytecode.Opcode;
+import javassist.bytecode.analysis.Analyzer;
+import javassist.bytecode.analysis.Frame;
+import javassist.bytecode.analysis.Type;
+import junit.framework.TestCase;
+
+/**
+ * Tests Analyzer
+ *
+ * @author Jason T. Greene
+ */
+public class AnalyzerTest extends TestCase {
+
+    public void testCommonSupperArray() throws Exception {
+        ClassPool pool = ClassPool.getDefault();
+        CtClass clazz = pool.get(getClass().getName() + "$Dummy");
+        CtMethod method = clazz.getDeclaredMethod("commonSuperArray");
+        verifyArrayLoad(clazz, method, "java.lang.Number");
+    }
+
+    public  void testCommonInterfaceArray() throws Exception {
+        ClassPool pool = ClassPool.getDefault();
+        CtClass clazz = pool.get(getClass().getName() + "$Dummy");
+        CtMethod method = clazz.getDeclaredMethod("commonInterfaceArray");
+        verifyArrayLoad(clazz, method, "java.io.Serializable");
+    }
+
+    public  void testSharedInterfaceAndSuperClass() throws Exception {
+        CtMethod method = ClassPool.getDefault().getMethod(
+                getClass().getName() + "$Dummy", "sharedInterfaceAndSuperClass");
+        verifyReturn(method, "java.io.Serializable");
+
+        method = ClassPool.getDefault().getMethod(
+                getClass().getName() + "$Dummy", "sharedOffsetInterfaceAndSuperClass");
+        verifyReturn(method, "java.io.Serializable");
+
+        method = ClassPool.getDefault().getMethod(
+                getClass().getName() + "$Dummy", "sharedSuperWithSharedInterface");
+        verifyReturn(method, getClass().getName() + "$Dummy$A");
+    }
+
+    public  void testArrayDifferentDims() throws Exception {
+        CtMethod method = ClassPool.getDefault().getMethod(
+                getClass().getName() + "$Dummy", "arrayDifferentDimensions1");
+        verifyReturn(method, "java.lang.Cloneable[]");
+
+        method = ClassPool.getDefault().getMethod(
+                getClass().getName() + "$Dummy", "arrayDifferentDimensions2");
+        verifyReturn(method, "java.lang.Object[][]");
+    }
+
+    public  void testReusedLocalMerge() throws Exception {
+        CtMethod method = ClassPool.getDefault().getMethod(
+                getClass().getName() + "$Dummy", "reusedLocalMerge");
+
+        MethodInfo info = method.getMethodInfo2();
+        Analyzer analyzer = new Analyzer();
+        Frame[] frames = analyzer.analyze(method.getDeclaringClass(), info);
+        assertNotNull(frames);
+        int pos = findOpcode(info, Opcode.RETURN);
+        Frame frame = frames[pos];
+        assertEquals("java.lang.Object", frame.getLocal(2).getCtClass().getName());
+    }
+
+    private static int findOpcode(MethodInfo info, int opcode) throws BadBytecode {
+        CodeIterator iter = info.getCodeAttribute().iterator();
+
+        // find return
+        int pos = 0;
+        while (iter.hasNext()) {
+            pos = iter.next();
+            if (iter.byteAt(pos) == opcode)
+                break;
+        }
+        return pos;
+    }
+
+
+    private static void verifyReturn(CtMethod method, String expected) throws BadBytecode {
+        MethodInfo info = method.getMethodInfo2();
+        CodeIterator iter = info.getCodeAttribute().iterator();
+
+        // find areturn
+        int pos = 0;
+        while (iter.hasNext()) {
+            pos = iter.next();
+            if (iter.byteAt(pos) == Opcode.ARETURN)
+                break;
+        }
+
+        Analyzer analyzer = new Analyzer();
+        Frame[] frames = analyzer.analyze(method.getDeclaringClass(), info);
+        assertNotNull(frames);
+        Frame frame = frames[pos];
+        assertEquals(expected, frame.peek().getCtClass().getName());
+    }
+
+    private static void verifyArrayLoad(CtClass clazz, CtMethod method, String component)
+            throws BadBytecode {
+        MethodInfo info = method.getMethodInfo2();
+        CodeIterator iter = info.getCodeAttribute().iterator();
+
+        // find aaload
+        int pos = 0;
+        while (iter.hasNext()) {
+            pos = iter.next();
+            if (iter.byteAt(pos) == Opcode.AALOAD)
+                break;
+        }
+
+        Analyzer analyzer = new Analyzer();
+        Frame[] frames = analyzer.analyze(clazz, info);
+        assertNotNull(frames);
+        Frame frame = frames[pos];
+        assertNotNull(frame);
+
+        Type type = frame.getStack(frame.getTopIndex() - 1);
+        assertEquals(component + "[]", type.getCtClass().getName());
+
+        pos = iter.next();
+        frame = frames[pos];
+        assertNotNull(frame);
+
+        type = frame.getStack(frame.getTopIndex());
+        assertEquals(component, type.getCtClass().getName());
+    }
+
+    private static void addJump(Bytecode code, int opcode, int pos) {
+        int current = code.currentPc();
+        code.addOpcode(opcode);
+        code.addIndex(pos - current);
+    }
+
+    public void testDeadCode() throws Exception {
+        CtMethod method = generateDeadCode(ClassPool.getDefault());
+        Analyzer analyzer = new Analyzer();
+        Frame[] frames = analyzer.analyze(method.getDeclaringClass(), method.getMethodInfo2());
+        assertNotNull(frames);
+        assertNull(frames[4]);
+        assertNotNull(frames[5]);
+        verifyReturn(method, "java.lang.String");
+    }
+
+    public void testInvalidCode() throws Exception {
+        CtMethod method = generateInvalidCode(ClassPool.getDefault());
+        Analyzer analyzer = new Analyzer();
+        try {
+            analyzer.analyze(method.getDeclaringClass(), method.getMethodInfo2());
+        } catch (BadBytecode e) {
+            return;
+        }
+
+        fail("Invalid code should have triggered a BadBytecode exception");
+    }
+
+    public void testCodeFalloff() throws Exception {
+        CtMethod method = generateCodeFalloff(ClassPool.getDefault());
+        Analyzer analyzer = new Analyzer();
+        try {
+            analyzer.analyze(method.getDeclaringClass(), method.getMethodInfo2());
+        } catch (BadBytecode e) {
+            return;
+        }
+
+        fail("Code falloff should have triggered a BadBytecode exception");
+    }
+
+    public void testJsrMerge() throws Exception {
+        CtMethod method = generateJsrMerge(ClassPool.getDefault());
+        Analyzer analyzer = new Analyzer();
+        analyzer.analyze(method.getDeclaringClass(), method.getMethodInfo2());
+        verifyReturn(method, "java.lang.String");
+    }
+
+    public void testJsrMerge2() throws Exception {
+        CtMethod method = generateJsrMerge2(ClassPool.getDefault());
+        Analyzer analyzer = new Analyzer();
+        analyzer.analyze(method.getDeclaringClass(), method.getMethodInfo2());
+        verifyReturn(method, "java.lang.String");
+    }
+
+    private CtMethod generateDeadCode(ClassPool pool) throws Exception {
+        CtClass clazz = pool.makeClass(getClass().getName() + "$Generated0");
+        CtClass stringClass = pool.get("java.lang.String");
+        CtMethod method = new CtMethod(stringClass, "foo", new CtClass[0], clazz);
+        MethodInfo info = method.getMethodInfo2();
+        info.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC);
+        Bytecode code = new Bytecode(info.getConstPool(), 1, 2);
+        /* 0 */ code.addIconst(1);
+        /* 1 */ addJump(code, Opcode.GOTO, 5);
+        /* 4 */ code.addIconst(0); // DEAD
+        /* 5 */ code.addIconst(1);
+        /* 6 */ code.addInvokestatic(stringClass, "valueOf", stringClass, new CtClass[]{CtClass.intType});
+        /* 9 */ code.addOpcode(Opcode.ARETURN);
+        info.setCodeAttribute(code.toCodeAttribute());
+        clazz.addMethod(method);
+
+        return method;
+    }
+
+    private CtMethod generateInvalidCode(ClassPool pool) throws Exception {
+        CtClass clazz = pool.makeClass(getClass().getName() + "$Generated4");
+        CtClass intClass = pool.get("java.lang.Integer");
+        CtClass stringClass = pool.get("java.lang.String");
+        CtMethod method = new CtMethod(stringClass, "foo", new CtClass[0], clazz);
+        MethodInfo info = method.getMethodInfo2();
+        info.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC);
+        Bytecode code = new Bytecode(info.getConstPool(), 1, 2);
+        /* 0 */ code.addIconst(1);
+        /* 1 */ code.addInvokestatic(intClass, "valueOf", intClass, new CtClass[]{CtClass.intType});
+        /* 4 */ code.addOpcode(Opcode.ARETURN);
+        info.setCodeAttribute(code.toCodeAttribute());
+        clazz.addMethod(method);
+
+        return method;
+    }
+
+
+    private CtMethod generateCodeFalloff(ClassPool pool) throws Exception {
+        CtClass clazz = pool.makeClass(getClass().getName() + "$Generated3");
+        CtClass stringClass = pool.get("java.lang.String");
+        CtMethod method = new CtMethod(stringClass, "foo", new CtClass[0], clazz);
+        MethodInfo info = method.getMethodInfo2();
+        info.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC);
+        Bytecode code = new Bytecode(info.getConstPool(), 1, 2);
+        /* 0 */ code.addIconst(1);
+        /* 1 */ code.addInvokestatic(stringClass, "valueOf", stringClass, new CtClass[]{CtClass.intType});
+        info.setCodeAttribute(code.toCodeAttribute());
+        clazz.addMethod(method);
+
+        return method;
+    }
+
+    private CtMethod generateJsrMerge(ClassPool pool) throws Exception {
+        CtClass clazz = pool.makeClass(getClass().getName() + "$Generated1");
+        CtClass stringClass = pool.get("java.lang.String");
+        CtMethod method = new CtMethod(stringClass, "foo", new CtClass[0], clazz);
+        MethodInfo info = method.getMethodInfo2();
+        info.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC);
+        Bytecode code = new Bytecode(info.getConstPool(), 1, 2);
+        /* 0 */ code.addIconst(5);
+        /* 1 */ code.addIstore(0);
+        /* 2 */ addJump(code, Opcode.JSR, 7);
+        /* 5 */ code.addAload(0);
+        /* 6 */ code.addOpcode(Opcode.ARETURN);
+        /* 7 */ code.addAstore(1);
+        /* 8 */ code.addIconst(3);
+        /* 9 */ code.addInvokestatic(stringClass, "valueOf", stringClass, new CtClass[]{CtClass.intType});
+        /* 12 */ code.addAstore(0);
+        /* 12 */ code.addRet(1);
+        info.setCodeAttribute(code.toCodeAttribute());
+        clazz.addMethod(method);
+        //System.out.println(clazz.toClass().getMethod("foo", new Class[0]).invoke(null, new Object[0]));
+
+        return method;
+    }
+
+    private CtMethod generateJsrMerge2(ClassPool pool) throws Exception {
+        CtClass clazz = pool.makeClass(getClass().getName() + "$Generated2");
+        CtClass stringClass = pool.get("java.lang.String");
+        CtMethod method = new CtMethod(stringClass, "foo", new CtClass[0], clazz);
+        MethodInfo info = method.getMethodInfo2();
+        info.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC);
+        Bytecode code = new Bytecode(info.getConstPool(), 1, 2);
+        /* 0 */ addJump(code, Opcode.JSR, 5);
+        /* 3 */ code.addAload(0);
+        /* 4 */ code.addOpcode(Opcode.ARETURN);
+        /* 5 */ code.addAstore(1);
+        /* 6 */ code.addIconst(4);
+        /* 7 */ code.addInvokestatic(stringClass, "valueOf", stringClass, new CtClass[]{CtClass.intType});
+        /* 10 */ code.addAstore(0);
+        /* 11 */ code.addRet(1);
+        info.setCodeAttribute(code.toCodeAttribute());
+        clazz.addMethod(method);
+
+        return method;
+    }
+
+    public static class Dummy {
+        public Serializable commonSuperArray(int x) {
+            Number[] n;
+
+            if (x > 5) {
+                n = new Long[10];
+            } else {
+                n = new Double[5];
+            }
+
+            return n[x];
+        }
+
+        public Serializable commonInterfaceArray(int x) {
+            Serializable[] n;
+
+            if (x > 5) {
+                n = new Long[10];
+            } else if (x > 3) {
+                n = new Double[5];
+            } else {
+                n = new String[3];
+            }
+
+            return n[x];
+        }
+
+
+        public static class A {};
+        public static class B1 extends A implements Serializable {};
+        public static class B2 extends A implements Serializable {};
+        public static class A2 implements Serializable, Cloneable {};
+        public static class A3 implements Serializable, Cloneable {};
+
+        public static class B3 extends A {};
+        public static class C31 extends B3 implements Serializable {};
+
+
+        public void dummy(Serializable s) {}
+
+        public Object sharedInterfaceAndSuperClass(int x) {
+            Serializable s;
+
+            if (x > 5) {
+                s = new B1();
+            } else {
+                s = new B2();
+            }
+
+            dummy(s);
+
+            return s;
+        }
+
+        public A sharedSuperWithSharedInterface(int x) {
+            A a;
+
+            if (x > 5) {
+                a = new B1();
+            } else if (x > 3) {
+                a = new B2();
+            } else {
+                a = new C31();
+            }
+
+            return a;
+        }
+
+
+        public void reusedLocalMerge() {
+             ArrayList list = new ArrayList();
+             try {
+               Iterator i = list.iterator();
+               i.hasNext();
+             } catch (Exception e) {
+             }
+        }
+
+        public Object sharedOffsetInterfaceAndSuperClass(int x) {
+            Serializable s;
+
+            if (x > 5) {
+                s = new B1();
+            } else {
+                s = new C31();
+            }
+
+            dummy(s);
+
+            return s;
+        }
+
+
+        public Object arrayDifferentDimensions1(int x) {
+            Object[] n;
+
+            if ( x > 5) {
+                n = new Number[1][1];
+            } else {
+                n = new Cloneable[1];
+            }
+
+
+            return n;
+        }
+
+        public Object arrayDifferentDimensions2(int x) {
+            Object[] n;
+
+            if ( x> 5) {
+                n = new String[1][1];
+            } else {
+                n = new Number[1][1][1][1];
+            }
+
+            return n;
+        }
+    }
+}
diff --git a/src/test/test/javassist/bytecode/analysis/ErrorFinder.java b/src/test/test/javassist/bytecode/analysis/ErrorFinder.java
new file mode 100644 (file)
index 0000000..a212832
--- /dev/null
@@ -0,0 +1,62 @@
+package test.javassist.bytecode.analysis;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.CtMethod;
+import javassist.bytecode.analysis.Analyzer;
+
+/**
+ * Simple testing tool that verifies class files can be analyzed.
+ *
+ * @author Jason T. Greene
+ */
+public class ErrorFinder {
+
+    public static void main(String[] args) throws Exception {
+        ClassPool pool = ClassPool.getDefault();
+
+        String className = args[0];
+        if (!className.equals("-file")) {
+            analyzeClass(pool, className);
+            return;
+        }
+
+        FileReader reader = new FileReader(args[1]);
+        BufferedReader lineReader = new BufferedReader(reader);
+
+
+        String line = lineReader.readLine();
+        while (line != null) {
+            analyzeClass(pool, line);
+            line = lineReader.readLine();
+        }
+    }
+
+    private static void analyzeClass(ClassPool pool, String className) {
+        try {
+
+            CtClass clazz = pool.get(className);
+            CtMethod[] methods = clazz.getDeclaredMethods();
+            for (int i = 0; i < methods.length; i++)
+                analyzeMethod(clazz, methods[i]);
+        } catch (Throwable e) {
+            System.out.println("FAIL: CLASS: " + className + " " + e.getClass() + ":" + e.getMessage());
+        }
+    }
+
+    private static void analyzeMethod(CtClass clazz, CtMethod method) {
+        String methodName = clazz.getName() + "." + method.getName() + method.getSignature();
+        System.out.println("START: " + methodName);
+        Analyzer analyzer = new Analyzer();
+
+        try {
+            analyzer.analyze(clazz, method.getMethodInfo2());
+            System.out.println("SUCCESS: " + methodName);
+        } catch (Exception e) {
+            System.out.println("FAIL: " + methodName + " - " + (e.getMessage() == null ? e.getClass().getName() : e.getMessage()));
+        }
+    }
+}
diff --git a/src/test/test/javassist/bytecode/analysis/ScannerTest.java b/src/test/test/javassist/bytecode/analysis/ScannerTest.java
new file mode 100644 (file)
index 0000000..56114ba
--- /dev/null
@@ -0,0 +1,185 @@
+package test.javassist.bytecode.analysis;
+
+import java.io.IOException;
+
+import javassist.CannotCompileException;
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.CtMethod;
+import javassist.NotFoundException;
+import javassist.bytecode.AccessFlag;
+import javassist.bytecode.Bytecode;
+import javassist.bytecode.MethodInfo;
+import javassist.bytecode.Opcode;
+import javassist.bytecode.analysis.Subroutine;
+import javassist.bytecode.analysis.SubroutineScanner;
+import junit.framework.TestCase;
+
+/**
+ * Tests Subroutine Scanner
+ *
+ * @author Jason T. Greene
+ */
+public class ScannerTest extends TestCase {
+
+    public void testNestedFinally() throws Exception {
+        ClassPool pool = ClassPool.getDefault();
+        generate(pool);
+        CtClass clazz = pool.get("test.ScannerTest$GeneratedTest");
+        CtMethod method = clazz.getDeclaredMethod("doit");
+
+        SubroutineScanner scanner = new SubroutineScanner();
+        Subroutine[] subs = scanner.scan(method.getMethodInfo2());
+
+        verifySubroutine(subs, 31, 31, new int[]{125, 25});
+        verifySubroutine(subs, 32, 31, new int[]{125, 25});
+        verifySubroutine(subs, 33, 31, new int[]{125, 25});
+        verifySubroutine(subs, 60, 31, new int[]{125, 25});
+        verifySubroutine(subs, 61, 31, new int[]{125, 25});
+        verifySubroutine(subs, 63, 31, new int[]{125, 25});
+        verifySubroutine(subs, 66, 31, new int[]{125, 25});
+        verifySubroutine(subs, 69, 31, new int[]{125, 25});
+        verifySubroutine(subs, 71, 31, new int[]{125, 25});
+        verifySubroutine(subs, 74, 31, new int[]{125, 25});
+        verifySubroutine(subs, 76, 31, new int[]{125, 25});
+        verifySubroutine(subs, 77, 77, new int[]{111, 71});
+        verifySubroutine(subs, 79, 77, new int[]{111, 71});
+        verifySubroutine(subs, 80, 77, new int[]{111, 71});
+        verifySubroutine(subs, 82, 77, new int[]{111, 71});
+        verifySubroutine(subs, 85, 77, new int[]{111, 71});
+        verifySubroutine(subs, 88, 77, new int[]{111, 71});
+        verifySubroutine(subs, 90, 77, new int[]{111, 71});
+        verifySubroutine(subs, 93, 77, new int[]{111, 71});
+        verifySubroutine(subs, 95, 77, new int[]{111, 71});
+        verifySubroutine(subs, 96, 96, new int[]{106, 90});
+        verifySubroutine(subs, 98, 96, new int[]{106, 90});
+        verifySubroutine(subs, 99, 96, new int[]{106, 90});
+        verifySubroutine(subs, 101, 96, new int[]{106, 90});
+        verifySubroutine(subs, 104, 96, new int[]{106, 90});
+        verifySubroutine(subs, 106, 77, new int[]{111, 71});
+        verifySubroutine(subs, 109, 77, new int[]{111, 71});
+        verifySubroutine(subs, 111, 31, new int[]{125, 25});
+        verifySubroutine(subs, 114, 31, new int[]{125, 25});
+        verifySubroutine(subs, 117, 31, new int[]{125, 25});
+        verifySubroutine(subs, 118, 31, new int[]{125, 25});
+        verifySubroutine(subs, 120, 31, new int[]{125, 25});
+        verifySubroutine(subs, 123, 31, new int[]{125, 25});
+    }
+
+    private static void verifySubroutine(Subroutine[] subs, int pos, int start,
+            int[] callers) {
+        Subroutine sub = subs[pos];
+        assertNotNull(sub);
+        assertEquals(sub.start(), start);
+        for (int i = 0; i < callers.length; i++)
+            assertTrue(sub.callers().contains(Integer.valueOf(callers[i])));
+    }
+
+    private static void generate(ClassPool pool) throws CannotCompileException, IOException, NotFoundException {
+        // Generated from eclipse JDK4 compiler:
+        // public void doit(int x) {
+        //    println("null");
+        //    try {
+        //        println("try");
+        //    } catch (RuntimeException e) {
+        //        e.printStackTrace();
+        //    } finally {
+        //        switch (x) {
+        //        default:
+        //        case 15:
+        //        try {
+        //            println("inner-try");
+        //        } finally {
+        //            try {
+        //                println("inner-inner-try");
+        //            } finally {
+        //                println("inner-finally");
+        //            }
+        //        }
+        //        break;
+        //        case 1789:
+        //        println("switch -17");
+        //        }
+        //    }
+        //}
+
+        CtClass clazz = pool.makeClass("test.ScannerTest$GeneratedTest");
+        CtMethod method = new CtMethod(CtClass.voidType, "doit", new CtClass[] {CtClass.intType}, clazz);
+        MethodInfo info = method.getMethodInfo2();
+        info.setAccessFlags(AccessFlag.PUBLIC);
+        CtClass stringClass = pool.get("java.lang.String");
+        Bytecode code = new Bytecode(info.getConstPool(), 2, 9);
+        /* 0   */ code.addAload(0);
+        /* 1   */ code.addLdc("start");
+        /* 3   */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass});
+        /* 6   */ code.addAload(0);
+        /* 7   */ code.addLdc("try");
+        /* 9   */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass});
+        /* 12  */ addJump(code, Opcode.GOTO, 125);
+        /* 14  */ code.addAstore(2);
+        /* 16  */ code.addAload(2);
+        /* 17  */ code.addInvokevirtual("java.lang.Exception", "printStackTrace", "()V");
+        /* 20  */ addJump(code, Opcode.GOTO, 125);
+        /* 23  */ code.addAstore(4);
+        /* 25  */ addJump(code, Opcode.JSR, 31);
+        /* 28  */ code.addAload(4);
+        /* 30  */ code.addOpcode(Opcode.ATHROW);
+        /* 31  */ code.addAstore(3);
+        /* 32  */ code.addIload(1);
+        int spos = code.currentPc();
+        /* 33  */ code.addOpcode(Opcode.LOOKUPSWITCH);
+                  code.addIndex(0); // 2 bytes pad - gets us to 36
+                  code.add32bit(60 - spos); // default
+                  code.add32bit(2); // 2 pairs
+                  code.add32bit(15); code.add32bit(60 - spos);
+                  code.add32bit(1789); code.add32bit(117 - spos);
+        /* 60  */ code.addAload(0);
+        /* 61  */ code.addLdc("inner-try");
+        /* 63  */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass});
+        /* 66  */ addJump(code, Opcode.GOTO, 111);
+        /* 69  */ code.addAstore(6);
+        /* 71  */ addJump(code, Opcode.JSR, 77);
+        /* 74  */ code.addAload(6);
+        /* 76  */ code.add(Opcode.ATHROW);
+        /* 77  */ code.addAstore(5);
+        /* 79  */ code.addAload(0);
+        /* 80  */ code.addLdc("inner-inner-try");
+        /* 82  */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass});
+        /* 85  */ addJump(code, Opcode.GOTO, 106);
+        /* 88  */ code.addAstore(8);
+        /* 90  */ addJump(code, Opcode.JSR, 96);
+        /* 93  */ code.addAload(8);
+        /* 95  */ code.add(Opcode.ATHROW);
+        /* 96  */ code.addAstore(7);
+        /* 98  */ code.addAload(0);
+        /* 99  */ code.addLdc("inner-finally");
+        /* 101 */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass});
+        /* 104 */ code.addRet(7);
+        /* 106 */ addJump(code, Opcode.JSR, 96);
+        /* 109 */ code.addRet(5);
+        /* 111 */ addJump(code, Opcode.JSR, 77);
+        /* 114 */ addJump(code, Opcode.GOTO, 123);
+        /* 117 */ code.addAload(0);
+        /* 118 */ code.addLdc("switch - 1789");
+        /* 120 */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass});
+        /* 123 */ code.addRet(3);
+        /* 125 */ addJump(code, Opcode.JSR, 31);
+        /* 128 */ code.addOpcode(Opcode.RETURN);
+        code.addExceptionHandler(6, 12, 15, "java.lang.RuntimeException");
+        code.addExceptionHandler(6, 20, 23, 0);
+        code.addExceptionHandler(125, 128, 23, 0);
+        code.addExceptionHandler(60, 69, 69, 0);
+        code.addExceptionHandler(111, 114, 69, 0);
+        code.addExceptionHandler(79, 88, 88, 0);
+        code.addExceptionHandler(106, 109, 88, 0);
+        info.setCodeAttribute(code.toCodeAttribute());
+        clazz.addMethod(method);
+        clazz.writeFile("/tmp");
+    }
+
+    private static void addJump(Bytecode code, int opcode, int pos) {
+        int current = code.currentPc();
+        code.addOpcode(opcode);
+        code.addIndex(pos - current);
+    }
+}
diff --git a/src/test/test/javassist/convert/ArrayAccessReplaceTest.java b/src/test/test/javassist/convert/ArrayAccessReplaceTest.java
new file mode 100644 (file)
index 0000000..4c40849
--- /dev/null
@@ -0,0 +1,407 @@
+package test.javassist.convert;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.HashMap;
+import java.util.Map;
+
+import javassist.ClassPool;
+import javassist.CodeConverter;
+import javassist.CtClass;
+import junit.framework.TestCase;
+
+public class ArrayAccessReplaceTest extends TestCase {
+    private static SimpleInterface simple;
+
+    public void setUp() throws Exception {
+        ClassPool pool = new ClassPool(true);
+        CtClass echoClass = pool.get(ArrayAccessReplaceTest.class.getName() + "$Echo");
+        CtClass simpleClass = pool.get(ArrayAccessReplaceTest.class.getName() + "$Simple");
+        CodeConverter converter = new CodeConverter();
+        converter.replaceArrayAccess(echoClass, new CodeConverter.DefaultArrayAccessReplacementMethodNames());
+        simpleClass.instrument(converter);
+        simple = (SimpleInterface) simpleClass.toClass(new URLClassLoader(new URL[0], getClass().getClassLoader()), Class.class.getProtectionDomain()).newInstance();
+    }
+
+    public void testComplex() throws Exception {
+        ClassPool pool = new ClassPool(true);
+        CtClass clazz = pool.get(ArrayAccessReplaceTest.class.getName() + "$Complex");
+
+        CodeConverter converter = new CodeConverter();
+        converter.replaceArrayAccess(clazz, new CodeConverter.DefaultArrayAccessReplacementMethodNames());
+        clazz.instrument(converter);
+        clazz.writeFile("/tmp");
+        ComplexInterface instance = (ComplexInterface) clazz.toClass(new URLClassLoader(new URL[0], getClass().getClassLoader()), Class.class.getProtectionDomain()).newInstance();
+        assertEquals(Integer.valueOf(5), instance.complexRead(4));
+    }
+
+    public void testBoolean() throws Exception {
+        for (int i = 0; i < 100; i++) {
+            boolean value = i % 5 == 0;
+            simple.setBoolean(i, value);
+        }
+
+        for (int i = 0; i < 100; i++) {
+            boolean value = i % 5 == 0;
+            assertEquals(value, simple.getBoolean(i));
+        }
+    }
+
+    public void testByte() throws Exception {
+        for (byte i = 0; i < 100; i++) {
+            simple.setByte(i, i);
+        }
+
+        for (byte i = 0; i < 100; i++) {
+            assertEquals(i, simple.getByte(i));
+        }
+    }
+
+    public void testShort() throws Exception {
+        for (short i = 0; i < 100; i++) {
+            simple.setShort(i, i);
+        }
+
+        for (short i = 0; i < 100; i++) {
+            assertEquals(i, simple.getShort(i));
+        }
+    }
+
+    public void testChar() throws Exception {
+        for (char i = 0; i < 100; i++) {
+            simple.setChar(i, i);
+        }
+
+        for (char i = 0; i < 100; i++) {
+            assertEquals(i, simple.getChar(i));
+        }
+    }
+
+    public void testInt() throws Exception {
+        for (int i = 0; i < 100; i++) {
+            simple.setInt(i, i);
+        }
+
+        for (int i = 0; i < 100; i++) {
+            assertEquals(i, simple.getInt(i));
+        }
+    }
+
+    public void testLong() throws Exception {
+        for (int i = 0; i < 100; i++) {
+            simple.setLong(i, i);
+        }
+
+        for (int i = 0; i < 100; i++) {
+            assertEquals(i, simple.getLong(i));
+        }
+    }
+
+    public void testFloat() throws Exception {
+        for (int i = 0; i < 100; i++) {
+            simple.setFloat(i, i);
+        }
+
+        for (int i = 0; i < 100; i++) {
+            assertEquals((float)i, simple.getFloat(i), 0);
+        }
+    }
+
+    public void testDouble() throws Exception {
+        for (int i = 0; i < 100; i++) {
+            simple.setDouble(i, i);
+        }
+
+        for (int i = 0; i < 100; i++) {
+            assertEquals((double)i, simple.getDouble(i), 0);
+        }
+    }
+
+    public void testObject() throws Exception {
+        for (int i = 0; i < 100; i++) {
+            simple.setObject(i, Integer.valueOf(i));
+        }
+
+        for (int i = 0; i < 100; i++) {
+            assertEquals(Integer.valueOf(i), simple.getObject(i));
+        }
+    }
+
+    public void testFoo() throws Exception {
+        for (int i = 0; i < 100; i++) {
+            simple.setFoo(i, new Foo(i));
+        }
+
+        for (int i = 0; i < 100; i++) {
+            assertEquals(new Foo(i), simple.getFoo(i));
+        }
+    }
+
+    public static class Echo {
+        public static Map byteMap = new HashMap();
+        public static Map charMap = new HashMap();
+        public static Map doubleMap = new HashMap();
+        public static Map floatMap = new HashMap();
+        public static Map intMap = new HashMap();
+        public static Map longMap = new HashMap();
+        public static Map objectMap = new HashMap();
+        public static Map shortMap = new HashMap();
+
+        public static Object arrayReadObject(Object array, int index) {
+            return objectMap.get(Integer.valueOf(index));
+        }
+
+        public static void arrayWriteObject(Object array, int index, Object element) {
+            objectMap.put(Integer.valueOf(index), element);
+        }
+
+        public static byte arrayReadByteOrBoolean(Object array, int index) {
+            return ((Byte)byteMap.get(Integer.valueOf(index))).byteValue();
+        }
+
+        public static void arrayWriteByteOrBoolean(Object array, int index, byte element) {
+            byteMap.put(Integer.valueOf(index), Byte.valueOf(element));
+        }
+
+        public static char arrayReadChar(Object array, int index) {
+            return ((Character)charMap.get(Integer.valueOf(index))).charValue();
+        }
+
+        public static void arrayWriteChar(Object array, int index, char element) {
+            charMap.put(Integer.valueOf(index), Character.valueOf(element));
+        }
+
+        public static double arrayReadDouble(Object array, int index) {
+            return ((Double)doubleMap.get(Integer.valueOf(index))).doubleValue();
+        }
+
+        public static void arrayWriteDouble(Object array, int index, double element) {
+            doubleMap.put(Integer.valueOf(index), Double.valueOf(element));
+        }
+
+        public static float arrayReadFloat(Object array, int index) {
+            return ((Float)floatMap.get(Integer.valueOf(index))).floatValue();
+        }
+
+        public static void arrayWriteFloat(Object array, int index, float element) {
+            floatMap.put(Integer.valueOf(index), Float.valueOf(element));
+        }
+
+        public static int arrayReadInt(Object array, int index) {
+            return ((Integer)intMap.get(Integer.valueOf(index))).intValue();
+        }
+
+        public static void arrayWriteInt(Object array, int index, int element) {
+            intMap.put(Integer.valueOf(index), Integer.valueOf(element));
+        }
+
+        public static long arrayReadLong(Object array, int index) {
+            return ((Long)longMap.get(Integer.valueOf(index))).longValue();
+        }
+
+        public static void arrayWriteLong(Object array, int index, long element) {
+            longMap.put(Integer.valueOf(index), Long.valueOf(element));
+        }
+
+        public static short arrayReadShort(Object array, int index) {
+            return ((Short)shortMap.get(Integer.valueOf(index))).shortValue();
+        }
+
+        public static void arrayWriteShort(Object array, int index, short element) {
+            shortMap.put(Integer.valueOf(index), Short.valueOf(element));
+        }
+    }
+
+    public static class Foo {
+        public int bar;
+
+        public Foo(int bar) {
+            this.bar = bar;
+        }
+
+        public int hashCode() {
+            return bar;
+        }
+
+        public boolean equals(Object o) {
+            if (! (o instanceof Foo))
+                return false;
+
+            return ((Foo)o).bar == bar;
+        }
+    }
+
+    public static interface SimpleInterface {
+        public void setBoolean(int pos, boolean value);
+        public boolean getBoolean(int pos);
+
+        public void setByte(int pos, byte value);
+        public byte getByte(int pos);
+
+        public void setShort(int pos, short value);
+        public short getShort(int pos);
+
+        public void setChar(int pos, char value);
+        public char getChar(int pos);
+
+        public void setInt(int pos, int value);
+        public int getInt(int pos);
+
+        public void setLong(int pos, long value);
+        public long getLong(int pos);
+
+        public void setFloat(int pos, float value);
+        public float getFloat(int pos);
+
+        public void setDouble(int pos, double value);
+        public double getDouble(int pos);
+
+        public void setObject(int pos, Object value);
+        public Object getObject(int pos);
+
+        public void setFoo(int pos, Foo value);
+        public Foo getFoo(int pos);
+    }
+
+    public static class Simple implements SimpleInterface {
+        private boolean[] booleans;
+        private byte[] bytes;
+        private short[] shorts;
+        private char[] chars;
+        private int[] ints;
+        private long[] longs;
+        private float[] floats;
+        private double[] doubles;
+        private Object[] objects;
+        private Foo[] foos;
+
+        public boolean getBoolean(int pos) {
+            return booleans[pos];
+        }
+
+        public byte getByte(int pos) {
+            return bytes[pos];
+        }
+
+        public char getChar(int pos) {
+            return chars[pos];
+        }
+
+        public double getDouble(int pos) {
+            return doubles[pos];
+        }
+
+        public float getFloat(int pos) {
+            return floats[pos];
+        }
+
+        public Foo getFoo(int pos) {
+            return foos[pos];
+        }
+
+        public int getInt(int pos) {
+            return ints[pos];
+        }
+
+        public long getLong(int pos) {
+            return longs[pos];
+        }
+
+        public Object getObject(int pos) {
+            return objects[pos];
+        }
+
+        public short getShort(int pos) {
+            return shorts[pos];
+        }
+
+        public void setBoolean(int pos, boolean value) {
+            booleans[pos] = value;
+        }
+
+        public void setByte(int pos, byte value) {
+            bytes[pos] = value;
+        }
+
+        public void setChar(int pos, char value) {
+            chars[pos] = value;
+        }
+
+        public void setDouble(int pos, double value) {
+            doubles[pos] = value;
+        }
+
+        public void setFloat(int pos, float value) {
+            floats[pos] = value;
+        }
+
+        public void setFoo(int pos, Foo value) {
+            foos[pos] = value;
+        }
+
+        public void setInt(int pos, int value) {
+            ints[pos] = value;
+        }
+
+        public void setLong(int pos, long value) {
+            longs[pos] = value;
+        }
+
+        public void setObject(int pos, Object value) {
+            objects[pos] = value;
+        }
+
+        public void setShort(int pos, short value) {
+            shorts[pos] = value;
+        }
+
+    }
+
+    public static interface ComplexInterface {
+        public Number complexRead(int x);
+    }
+
+    public static class Complex implements ComplexInterface {
+        private Integer[] nums;
+        private Long[] longNums;
+        private static Integer justRead;
+
+        public static Object arrayReadObject(Object array, int offset) {
+            return Integer.valueOf(justRead.intValue() + offset);
+        }
+
+        public static void arrayWriteObject(Object array, int offset, Object element) {
+            justRead = (Integer) element;
+        }
+
+        public Object getInteger(int i) {
+            return (Object) Integer.valueOf(i);
+        }
+
+        public Number complexRead(int x) {
+            Number[] ns = null;
+            Number n1, n2, n3, n4;
+            try {
+                ((Object[])ns)[1] = getInteger(x);
+                // We have to throw an error since we can't intercept
+                // a guaranteed null array read yet (likely never will be able to)
+                throw new Error("hi");
+            } catch (Error error) {
+                ns = nums;
+            } catch (Exception exception) {
+                ns = longNums;
+            } finally {
+                n1 = ns[1];
+                n2 = ns[2];
+                n3 = ns[3];
+                n4 = ns[4];
+
+                n2.intValue();
+                n3.intValue();
+                n4.intValue();
+            }
+
+            return n1;
+        }
+    }
+}
\ No newline at end of file