<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>
<property environment="env"/>
<property name="target.jar" value="javassist.jar"/>
<property name="target-src.jar" value="javassist-src.jar"/>
+ <property name="lib.dir" value="${basedir}/lib"/>
<property name="src.dir" value="${basedir}/src/main"/>
<property name="build.dir" value="${basedir}/build"/>
<property name="build.classes.dir" value="${build.dir}/classes"/>
+ <property name="test.src.dir" value="${basedir}/src/test"/>
+ <property name="test.build.dir" value="${basedir}/build/test-classes"/>
+ <property name="test.reports.dir" value = "${basedir}/build/test-output"/>
<property name="run.dir" value="${build.classes.dir}"/>
<property name="build.classpath" refid="classpath"/>
+ <path id="test.compile.classpath">
+ <pathelement location="${build.classes.dir}"/>
+ <pathelement location="${lib.dir}/junit.jar"/>
+ </path>
+
+ <property name="test.compile.classpath" refid="test.compile.classpath"/>
+
+ <path id="test.classpath">
+ <pathelement location="${test.build.dir}"/>
+ <pathelement location="${lib.dir}/junit.jar"/>
+ <pathelement location="${build.classes.dir}"/>
+ </path>
+
+ <property name="test.classpath" refid="test.classpath"/>
+
<!-- =================================================================== -->
<!-- Prepares the build directory -->
<!-- =================================================================== -->
<target name="prepare" >
<mkdir dir="${build.dir}"/>
<mkdir dir="${build.classes.dir}"/>
+ <mkdir dir="${test.build.dir}"/>
+ <mkdir dir="${test.reports.dir}"/>
</target>
<!-- =================================================================== -->
</javac>
</target>
+ <target name="test-compile" depends="compile">
+ <javac srcdir="${test.src.dir}"
+ destdir="${test.build.dir}"
+ debug="on"
+ deprecation="on"
+ optimize="off"
+ includes="**">
+ <classpath refid="test.compile.classpath"/>
+ </javac>
+ </target>
+
+ <target name="test" depends="test-compile">
+ <junit fork="true" printsummary="true">
+ <classpath refid="test.classpath"/>
+ <formatter type="plain"/>
+ <formatter type="xml"/>
+ <batchtest todir="${test.reports.dir}">
+ <fileset dir="${test.build.dir}">
+ <include name="**/*Test.*"/>
+ </fileset>
+ </batchtest>
+ </junit>
+ </target>
+
<target name="sample" depends="compile">
<javac srcdir="${basedir}"
destdir="${build.classes.dir}"
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()) {
* 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
{
* 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
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()
public BadBytecode(String msg) {
super(msg);
}
+
+ public BadBytecode(String msg, Throwable cause) {
+ super(msg, cause);
+ }
}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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 + "]");
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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(' ');
+ }
+
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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);
+ }
+ }
+
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+<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>
*/
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);
+ }
}
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
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() {}
--- /dev/null
+/*
+ * 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);
+ }
+}
+++ /dev/null
-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("");
- }
-}
--- /dev/null
+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;
+ }
+ }
+}
--- /dev/null
+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()));
+ }
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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