diff options
author | jgreene <jgreene@30ef5769-5b8d-40dd-aea6-55b5d6557bb3> | 2008-05-24 05:13:20 +0000 |
---|---|---|
committer | jgreene <jgreene@30ef5769-5b8d-40dd-aea6-55b5d6557bb3> | 2008-05-24 05:13:20 +0000 |
commit | 2bae8b13291b8ef8fc3df24af627424f344581df (patch) | |
tree | 7d68840873257ab78ae69cc9ed9570adc18bc2d8 | |
parent | e48604525140c746404a637282cb7b1dd97c18c4 (diff) | |
download | javassist-2bae8b13291b8ef8fc3df24af627424f344581df.tar.gz javassist-2bae8b13291b8ef8fc3df24af627424f344581df.zip |
Fix subtypeOf in CtArray
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, 4928 insertions, 166 deletions
@@ -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> @@ -11,9 +11,13 @@ <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}"/> @@ -24,12 +28,29 @@ <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> <!-- =================================================================== --> @@ -46,6 +67,30 @@ </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 Binary files differnew file mode 100644 index 00000000..674d71e8 --- /dev/null +++ b/lib/junit.jar diff --git a/src/main/javassist/CodeConverter.java b/src/main/javassist/CodeConverter.java index 74dcb8ff..4ffe1eeb 100644 --- a/src/main/javassist/CodeConverter.java +++ b/src/main/javassist/CodeConverter.java @@ -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 diff --git a/src/main/javassist/CtArray.java b/src/main/javassist/CtArray.java index e46f24b5..55925483 100644 --- a/src/main/javassist/CtArray.java +++ b/src/main/javassist/CtArray.java @@ -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() diff --git a/src/main/javassist/bytecode/BadBytecode.java b/src/main/javassist/bytecode/BadBytecode.java index 4241fbd6..2f93b52d 100644 --- a/src/main/javassist/bytecode/BadBytecode.java +++ b/src/main/javassist/bytecode/BadBytecode.java @@ -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 index 00000000..03d03908 --- /dev/null +++ b/src/main/javassist/bytecode/InstructionPrinter.java @@ -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 index 00000000..7ef0b00b --- /dev/null +++ b/src/main/javassist/bytecode/analysis/Analyzer.java @@ -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 index 00000000..7cf9a679 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/Executor.java @@ -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 index 00000000..cf646f4b --- /dev/null +++ b/src/main/javassist/bytecode/analysis/Frame.java @@ -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 index 00000000..263e5299 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/FramePrinter.java @@ -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 index 00000000..d50cddc3 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/IntQueue.java @@ -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 index 00000000..750116c6 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/MultiArrayType.java @@ -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 index 00000000..31486ea4 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/MultiType.java @@ -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 index 00000000..924d37a6 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/Subroutine.java @@ -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 index 00000000..3feae539 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/SubroutineScanner.java @@ -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 index 00000000..38df93f7 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/Type.java @@ -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 index 00000000..a8cdfcca --- /dev/null +++ b/src/main/javassist/bytecode/analysis/Util.java @@ -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 index 00000000..b141670b --- /dev/null +++ b/src/main/javassist/bytecode/analysis/package.html @@ -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> diff --git a/src/main/javassist/convert/TransformAccessArrayField.java b/src/main/javassist/convert/TransformAccessArrayField.java index d3036056..1af424ed 100644 --- a/src/main/javassist/convert/TransformAccessArrayField.java +++ b/src/main/javassist/convert/TransformAccessArrayField.java @@ -14,168 +14,251 @@ */ 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); + } } diff --git a/src/main/javassist/convert/Transformer.java b/src/main/javassist/convert/Transformer.java index d4ea0ecf..1095cf5b 100644 --- a/src/main/javassist/convert/Transformer.java +++ b/src/main/javassist/convert/Transformer.java @@ -15,9 +15,14 @@ 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 index 00000000..0a30fb1d --- /dev/null +++ b/src/main/javassist/tools/framedump.java @@ -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 index 6d34165c..00000000 --- a/src/test/test/Test.java +++ /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 index 00000000..0c5a77e6 --- /dev/null +++ b/src/test/test/javassist/bytecode/analysis/AnalyzerTest.java @@ -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 index 00000000..a2128325 --- /dev/null +++ b/src/test/test/javassist/bytecode/analysis/ErrorFinder.java @@ -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 index 00000000..56114baf --- /dev/null +++ b/src/test/test/javassist/bytecode/analysis/ScannerTest.java @@ -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 index 00000000..4c40849c --- /dev/null +++ b/src/test/test/javassist/convert/ArrayAccessReplaceTest.java @@ -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 |