diff options
Diffstat (limited to 'src/main/javassist')
19 files changed, 3816 insertions, 155 deletions
diff --git a/src/main/javassist/CodeConverter.java b/src/main/javassist/CodeConverter.java index 74dcb8ff..4ffe1eeb 100644 --- a/src/main/javassist/CodeConverter.java +++ b/src/main/javassist/CodeConverter.java @@ -470,9 +470,8 @@ public class CodeConverter { CodeAttribute codeAttr = minfo.getCodeAttribute(); if (codeAttr == null || transformers == null) return; - for (t = transformers; t != null; t = t.getNext()) - t.initialize(cp, codeAttr); + t.initialize(cp, clazz, minfo); CodeIterator iterator = codeAttr.iterator(); while (iterator.hasNext()) { @@ -504,7 +503,7 @@ public class CodeConverter { * as array access replacements. * * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> - * @version $Revision: 1.13 $ + * @version $Revision: 1.14 $ */ public interface ArrayAccessReplacementMethodNames { @@ -613,7 +612,7 @@ public class CodeConverter { * accesses to array elements. * * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> - * @version $Revision: 1.13 $ + * @version $Revision: 1.14 $ */ public static class DefaultArrayAccessReplacementMethodNames implements ArrayAccessReplacementMethodNames diff --git a/src/main/javassist/CtArray.java b/src/main/javassist/CtArray.java index e46f24b5..55925483 100644 --- a/src/main/javassist/CtArray.java +++ b/src/main/javassist/CtArray.java @@ -41,7 +41,8 @@ final class CtArray extends CtClass { String cname = clazz.getName(); if (cname.equals(javaLangObject) - || cname.equals("java.lang.Cloneable")) + || cname.equals("java.lang.Cloneable") + || cname.equals("java.io.Serializable")) return true; return clazz.isArray() diff --git a/src/main/javassist/bytecode/BadBytecode.java b/src/main/javassist/bytecode/BadBytecode.java index 4241fbd6..2f93b52d 100644 --- a/src/main/javassist/bytecode/BadBytecode.java +++ b/src/main/javassist/bytecode/BadBytecode.java @@ -26,4 +26,8 @@ public class BadBytecode extends Exception { public BadBytecode(String msg) { super(msg); } + + public BadBytecode(String msg, Throwable cause) { + super(msg, cause); + } } diff --git a/src/main/javassist/bytecode/InstructionPrinter.java b/src/main/javassist/bytecode/InstructionPrinter.java new file mode 100644 index 00000000..03d03908 --- /dev/null +++ b/src/main/javassist/bytecode/InstructionPrinter.java @@ -0,0 +1,280 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode; + +import java.io.PrintStream; + +import javassist.CtMethod; + +/** + * Simple utility class for printing the instructions of a method. + * + * @author Jason T. Greene + */ +public class InstructionPrinter implements Opcode { + + private final static String opcodes[] = Mnemonic.OPCODE; + private final PrintStream stream; + + public InstructionPrinter(PrintStream stream) { + this.stream = stream; + } + + public static void print(CtMethod method, PrintStream stream) { + (new InstructionPrinter(stream)).print(method); + } + + public void print(CtMethod method) { + MethodInfo info = method.getMethodInfo2(); + ConstPool pool = info.getConstPool(); + CodeAttribute code = info.getCodeAttribute(); + if (code == null) + return; + + CodeIterator iterator = code.iterator(); + while (iterator.hasNext()) { + int pos; + try { + pos = iterator.next(); + } catch (BadBytecode e) { + throw new RuntimeException(e); + } + + stream.println(pos + ": " + instructionString(iterator, pos, pool)); + } + } + + public static String instructionString(CodeIterator iter, int pos, ConstPool pool) { + int opcode = iter.byteAt(pos); + + if (opcode > opcodes.length || opcode < 0) + throw new IllegalArgumentException("Invalid opcode, opcode: " + opcode + " pos: "+ pos); + + String opstring = opcodes[opcode]; + switch (opcode) { + case BIPUSH: + return opstring + " " + iter.byteAt(pos + 1); + case SIPUSH: + return opstring + " " + iter.s16bitAt(pos + 1); + case LDC: + return opstring + " " + ldc(pool, iter.byteAt(pos + 1)); + case LDC_W : + case LDC2_W : + return opstring + " " + ldc(pool, iter.u16bitAt(pos + 1)); + case ILOAD: + case LLOAD: + case FLOAD: + case DLOAD: + case ALOAD: + case ISTORE: + case LSTORE: + case FSTORE: + case DSTORE: + case ASTORE: + return opstring + " " + iter.byteAt(pos + 1); + case IFEQ: + case IFGE: + case IFGT: + case IFLE: + case IFLT: + case IFNE: + case IFNONNULL: + case IFNULL: + case IF_ACMPEQ: + case IF_ACMPNE: + case IF_ICMPEQ: + case IF_ICMPGE: + case IF_ICMPGT: + case IF_ICMPLE: + case IF_ICMPLT: + case IF_ICMPNE: + return opstring + " " + (iter.s16bitAt(pos + 1) + pos); + case IINC: + return opstring + " " + iter.byteAt(pos + 1); + case GOTO: + case JSR: + return opstring + " " + (iter.s16bitAt(pos + 1) + pos); + case RET: + return opstring + " " + iter.byteAt(pos + 1); + case TABLESWITCH: + return tableSwitch(iter, pos); + case LOOKUPSWITCH: + return lookupSwitch(iter, pos); + case GETSTATIC: + case PUTSTATIC: + case GETFIELD: + case PUTFIELD: + return opstring + " " + fieldInfo(pool, iter.u16bitAt(pos + 1)); + case INVOKEVIRTUAL: + case INVOKESPECIAL: + case INVOKESTATIC: + return opstring + " " + methodInfo(pool, iter.u16bitAt(pos + 1)); + case INVOKEINTERFACE: + return opstring + " " + interfaceMethodInfo(pool, iter.u16bitAt(pos + 1)); + case 186: + throw new RuntimeException("Bad opcode 186"); + case NEW: + return opstring + " " + classInfo(pool, iter.u16bitAt(pos + 1)); + case NEWARRAY: + return opstring + " " + arrayInfo(iter.byteAt(pos + 1)); + case ANEWARRAY: + return opstring + " " + classInfo(pool, iter.u16bitAt(pos + 1)); + case WIDE: + return wide(iter, pos); + case MULTIANEWARRAY: + return opstring + " " + classInfo(pool, iter.u16bitAt(pos + 1)); + case GOTO_W: + case JSR_W: + return opstring + " " + (iter.s32bitAt(pos + 1)+ pos); + default: + return opstring; + } + } + + + private static String wide(CodeIterator iter, int pos) { + int opcode = iter.byteAt(pos + 1); + int index = iter.u16bitAt(pos + 2); + switch (opcode) { + case ILOAD: + case LLOAD: + case FLOAD: + case DLOAD: + case ALOAD: + case ISTORE: + case LSTORE: + case FSTORE: + case DSTORE: + case ASTORE: + case IINC: + case RET: + return opcodes[opcode] + " " + index; + default: + throw new RuntimeException("Invalid WIDE operand"); + } + } + + + private static String arrayInfo(int type) { + switch (type) { + case T_BOOLEAN: + return "boolean"; + case T_CHAR: + return "char"; + case T_BYTE: + return "byte"; + case T_SHORT: + return "short"; + case T_INT: + return "int"; + case T_LONG: + return "long"; + case T_FLOAT: + return "float"; + case T_DOUBLE: + return "double"; + default: + throw new RuntimeException("Invalid array type"); + } + } + + + private static String classInfo(ConstPool pool, int index) { + return "#" + index + " = Class " + pool.getClassInfo(index); + } + + + private static String interfaceMethodInfo(ConstPool pool, int index) { + return "#" + index + " = Method " + + pool.getInterfaceMethodrefClassName(index) + "." + + pool.getInterfaceMethodrefName(index) + "(" + + pool.getInterfaceMethodrefType(index) + ")"; + } + + private static String methodInfo(ConstPool pool, int index) { + return "#" + index + " = Method " + + pool.getMethodrefClassName(index) + "." + + pool.getMethodrefName(index) + "(" + + pool.getMethodrefType(index) + ")"; + } + + + private static String fieldInfo(ConstPool pool, int index) { + return "#" + index + " = Field " + + pool.getFieldrefClassName(index) + "." + + pool.getFieldrefName(index) + "(" + + pool.getFieldrefType(index) + ")"; + } + + + private static String lookupSwitch(CodeIterator iter, int pos) { + StringBuffer buffer = new StringBuffer("lookupswitch {\n"); + int index = (pos & ~3) + 4; + // default + buffer.append("\t\tdefault: ").append(pos + iter.s32bitAt(index)).append("\n"); + int npairs = iter.s32bitAt(index += 4); + int end = npairs * 8 + (index += 4); + + for (; index < end; index += 8) { + int match = iter.s32bitAt(index); + int target = iter.s32bitAt(index + 4) + pos; + buffer.append("\t\t").append(match).append(": ").append(target).append("\n"); + } + + buffer.setCharAt(buffer.length() - 1, '}'); + return buffer.toString(); + } + + + private static String tableSwitch(CodeIterator iter, int pos) { + StringBuffer buffer = new StringBuffer("tableswitch {\n"); + int index = (pos & ~3) + 4; + // default + buffer.append("\t\tdefault: ").append(pos + iter.s32bitAt(index)).append("\n"); + int low = iter.s32bitAt(index += 4); + int high = iter.s32bitAt(index += 4); + int end = (high - low + 1) * 4 + (index += 4); + + // Offset table + for (int key = low; index < end; index += 4, key++) { + int target = iter.s32bitAt(index) + pos; + buffer.append("\t\t").append(key).append(": ").append(target).append("\n"); + } + + buffer.setCharAt(buffer.length() - 1, '}'); + return buffer.toString(); + } + + + private static String ldc(ConstPool pool, int index) { + int tag = pool.getTag(index); + switch (tag) { + case ConstPool.CONST_String: + return "#" + index + " = \"" + pool.getStringInfo(index) + "\""; + case ConstPool.CONST_Integer: + return "#" + index + " = int " + pool.getIntegerInfo(index); + case ConstPool.CONST_Float: + return "#" + index + " = float " + pool.getFloatInfo(index); + case ConstPool.CONST_Long: + return "#" + index + " = long " + pool.getLongInfo(index); + case ConstPool.CONST_Double: + return "#" + index + " = int " + pool.getDoubleInfo(index); + case ConstPool.CONST_Class: + return classInfo(pool, index); + default: + throw new RuntimeException("bad LDC: " + tag); + } + } +} diff --git a/src/main/javassist/bytecode/analysis/Analyzer.java b/src/main/javassist/bytecode/analysis/Analyzer.java new file mode 100644 index 00000000..7ef0b00b --- /dev/null +++ b/src/main/javassist/bytecode/analysis/Analyzer.java @@ -0,0 +1,422 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + +import java.util.Iterator; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; +import javassist.bytecode.AccessFlag; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.CodeAttribute; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import javassist.bytecode.ExceptionTable; +import javassist.bytecode.MethodInfo; +import javassist.bytecode.Opcode; + +/** + * A data-flow analyzer that determines the type state of the stack and local + * variable table at every reachable instruction in a method. During analysis, + * bytecode verification is performed in a similar manner to that described + * in the JVM specification. + * + * <p>Example:</p> + * + * <pre> + * // Method to analyze + * public Object doSomething(int x) { + * Number n; + * if (x < 5) { + * n = new Double(0); + * } else { + * n = new Long(0); + * } + * + * return n; + * } + * + * // Which compiles to: + * // 0: iload_1 + * // 1: iconst_5 + * // 2: if_icmpge 17 + * // 5: new #18; //class java/lang/Double + * // 8: dup + * // 9: dconst_0 + * // 10: invokespecial #44; //Method java/lang/Double."<init>":(D)V + * // 13: astore_2 + * // 14: goto 26 + * // 17: new #16; //class java/lang/Long + * // 20: dup + * // 21: lconst_1 + * // 22: invokespecial #47; //Method java/lang/Long."<init>":(J)V + * // 25: astore_2 + * // 26: aload_2 + * // 27: areturn + * + * public void analyzeIt(CtClass clazz, MethodInfo method) { + * Analyzer analyzer = new Analyzer(); + * Frame[] frames = analyzer.analyze(clazz, method); + * frames[0].getLocal(0).getCtClass(); // returns clazz; + * frames[0].getLocal(1).getCtClass(); // returns java.lang.String + * frames[1].peek(); // returns Type.INTEGER + * frames[27].peek().getCtClass(); // returns java.lang.Number + * } + * </pre> + * + * @see FramePrinter + * @author Jason T. Greene + */ +public class Analyzer implements Opcode { + private final SubroutineScanner scanner = new SubroutineScanner(); + private CtClass clazz; + private ExceptionInfo[] exceptions; + private Frame[] frames; + private Subroutine[] subroutines; + + private static class ExceptionInfo { + private int end; + private int handler; + private int start; + private Type type; + + private ExceptionInfo(int start, int end, int handler, Type type) { + this.start = start; + this.end = end; + this.handler = handler; + this.type = type; + } + } + + /** + * Performs data-flow analysis on a method and returns an array, indexed by + * instruction position, containing the starting frame state of all reachable + * instructions. Non-reachable code, and illegal code offsets are represented + * as a null in the frame state array. This can be used to detect dead code. + * + * If the method does not contain code (it is either native or abstract), null + * is returned. + * + * @param clazz the declaring class of the method + * @param method the method to analyze + * @return an array, indexed by instruction position, of the starting frame state, + * or null if this method doesn't have code + * @throws BadBytecode if the bytecode does not comply with the JVM specification + */ + public Frame[] analyze(CtClass clazz, MethodInfo method) throws BadBytecode { + this.clazz = clazz; + CodeAttribute codeAttribute = method.getCodeAttribute(); + // Native or Abstract + if (codeAttribute == null) + return null; + + int maxLocals = codeAttribute.getMaxLocals(); + int maxStack = codeAttribute.getMaxStack(); + int codeLength = codeAttribute.getCodeLength(); + + CodeIterator iter = codeAttribute.iterator(); + IntQueue queue = new IntQueue(); + + exceptions = buildExceptionInfo(method); + subroutines = scanner.scan(method); + + Executor executor = new Executor(clazz.getClassPool(), method.getConstPool()); + frames = new Frame[codeLength]; + frames[iter.lookAhead()] = firstFrame(method, maxLocals, maxStack); + queue.add(iter.next()); + while (!queue.isEmpty()) { + analyzeNextEntry(method, iter, queue, executor); + } + + return frames; + } + + /** + * Performs data-flow analysis on a method and returns an array, indexed by + * instruction position, containing the starting frame state of all reachable + * instructions. Non-reachable code, and illegal code offsets are represented + * as a null in the frame state array. This can be used to detect dead code. + * + * If the method does not contain code (it is either native or abstract), null + * is returned. + * + * @param method the method to analyze + * @return an array, indexed by instruction position, of the starting frame state, + * or null if this method doesn't have code + * @throws BadBytecode if the bytecode does not comply with the JVM specification + */ + public Frame[] analyze(CtMethod method) throws BadBytecode { + return analyze(method.getDeclaringClass(), method.getMethodInfo2()); + } + + private void analyzeNextEntry(MethodInfo method, CodeIterator iter, + IntQueue queue, Executor executor) throws BadBytecode { + int pos = queue.take(); + iter.move(pos); + iter.next(); + + Frame frame = frames[pos].copy(); + Subroutine subroutine = subroutines[pos]; + + try { + executor.execute(method, pos, iter, frame, subroutine); + } catch (RuntimeException e) { + throw new BadBytecode(e.getMessage() + "[pos = " + pos + "]", e); + } + + int opcode = iter.byteAt(pos); + + if (opcode == TABLESWITCH) { + mergeTableSwitch(queue, pos, iter, frame); + } else if (opcode == LOOKUPSWITCH) { + mergeLookupSwitch(queue, pos, iter, frame); + } else if (opcode == RET) { + mergeRet(queue, iter, pos, frame, subroutine); + } else if (Util.isJumpInstruction(opcode)) { + int target = Util.getJumpTarget(pos, iter); + + if (Util.isJsr(opcode)) { + // Merge the state before the jsr into the next instruction + mergeJsr(queue, frames[pos], subroutines[target], pos, lookAhead(iter, pos)); + } else if (! Util.isGoto(opcode)) { + merge(queue, frame, lookAhead(iter, pos)); + } + + merge(queue, frame, target); + } else if (opcode != ATHROW && ! Util.isReturn(opcode)) { + // Can advance to next instruction + merge(queue, frame, lookAhead(iter, pos)); + } + + // Merge all exceptions that are reachable from this instruction. + // The redundancy is intentional, since the state must be based + // on the current instruction frame. + mergeExceptionHandlers(queue, method, pos, frame); + } + + private ExceptionInfo[] buildExceptionInfo(MethodInfo method) { + ConstPool constPool = method.getConstPool(); + ClassPool classes = clazz.getClassPool(); + + ExceptionTable table = method.getCodeAttribute().getExceptionTable(); + ExceptionInfo[] exceptions = new ExceptionInfo[table.size()]; + for (int i = 0; i < table.size(); i++) { + int index = table.catchType(i); + Type type; + try { + type = index == 0 ? Type.THROWABLE : Type.get(classes.get(constPool.getClassInfo(index))); + } catch (NotFoundException e) { + throw new IllegalStateException(e); + } + + exceptions[i] = new ExceptionInfo(table.startPc(i), table.endPc(i), table.handlerPc(i), type); + } + + return exceptions; + } + + private Frame firstFrame(MethodInfo method, int maxLocals, int maxStack) { + int pos = 0; + + Frame first = new Frame(maxLocals, maxStack); + if ((method.getAccessFlags() & AccessFlag.STATIC) == 0) { + first.setLocal(pos++, Type.get(clazz)); + } + + CtClass[] parameters; + try { + parameters = Descriptor.getParameterTypes(method.getDescriptor(), clazz.getClassPool()); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + + for (int i = 0; i < parameters.length; i++) { + Type type = zeroExtend(Type.get(parameters[i])); + first.setLocal(pos++, type); + if (type.getSize() == 2) + first.setLocal(pos++, Type.TOP); + } + + return first; + } + + private int getNext(CodeIterator iter, int of, int restore) throws BadBytecode { + iter.move(of); + iter.next(); + int next = iter.lookAhead(); + iter.move(restore); + iter.next(); + + return next; + } + + private int lookAhead(CodeIterator iter, int pos) throws BadBytecode { + if (! iter.hasNext()) + throw new BadBytecode("Execution falls off end! [pos = " + pos + "]"); + + return iter.lookAhead(); + } + + + private void merge(IntQueue queue, Frame frame, int target) { + Frame old = frames[target]; + boolean changed; + + if (old == null) { + frames[target] = frame.copy(); + changed = true; + } else { + changed = old.merge(frame); + } + + if (changed) { + queue.add(target); + } + } + + private void mergeExceptionHandlers(IntQueue queue, MethodInfo method, int pos, Frame frame) { + for (int i = 0; i < exceptions.length; i++) { + ExceptionInfo exception = exceptions[i]; + + // Start is inclusive, while end is exclusive! + if (pos >= exception.start && pos < exception.end) { + Frame newFrame = frame.copy(); + newFrame.clearStack(); + newFrame.push(exception.type); + merge(queue, newFrame, exception.handler); + } + } + } + + private void mergeJsr(IntQueue queue, Frame frame, Subroutine sub, int pos, int next) throws BadBytecode { + if (sub == null) + throw new BadBytecode("No subroutine at jsr target! [pos = " + pos + "]"); + + Frame old = frames[next]; + boolean changed = false; + + if (old == null) { + old = frames[next] = frame.copy(); + changed = true; + } else { + for (int i = 0; i < frame.localsLength(); i++) { + // Skip everything accessed by a subroutine, mergeRet must handle this + if (!sub.isAccessed(i)) { + Type oldType = old.getLocal(i); + Type newType = frame.getLocal(i); + if (oldType == null) { + old.setLocal(i, newType); + changed = true; + continue; + } + + newType = oldType.merge(newType); + // Always set the type, in case a multi-type switched to a standard type. + old.setLocal(i, newType); + if (!newType.equals(oldType) || newType.popChanged()) + changed = true; + } + } + } + + if (! old.isJsrMerged()) { + old.setJsrMerged(true); + changed = true; + } + + if (changed && old.isRetMerged()) + queue.add(next); + + } + + private void mergeLookupSwitch(IntQueue queue, int pos, CodeIterator iter, Frame frame) throws BadBytecode { + int index = (pos & ~3) + 4; + // default + merge(queue, frame, pos + iter.s32bitAt(index)); + int npairs = iter.s32bitAt(index += 4); + int end = npairs * 8 + (index += 4); + + // skip "match" + for (index += 4; index < end; index += 8) { + int target = iter.s32bitAt(index) + pos; + merge(queue, frame, target); + } + } + + private void mergeRet(IntQueue queue, CodeIterator iter, int pos, Frame frame, Subroutine subroutine) throws BadBytecode { + if (subroutine == null) + throw new BadBytecode("Ret on no subroutine! [pos = " + pos + "]"); + + Iterator callerIter = subroutine.callers().iterator(); + while (callerIter.hasNext()) { + int caller = ((Integer) callerIter.next()).intValue(); + int returnLoc = getNext(iter, caller, pos); + boolean changed = false; + + Frame old = frames[returnLoc]; + if (old == null) { + old = frames[returnLoc] = frame.copyStack(); + changed = true; + } else { + changed = old.mergeStack(frame); + } + + for (Iterator i = subroutine.accessed().iterator(); i.hasNext(); ) { + int index = ((Integer)i.next()).intValue(); + Type oldType = old.getLocal(index); + Type newType = frame.getLocal(index); + if (oldType != newType) { + old.setLocal(index, newType); + changed = true; + } + } + + if (! old.isRetMerged()) { + old.setRetMerged(true); + changed = true; + } + + if (changed && old.isJsrMerged()) + queue.add(returnLoc); + } + } + + + private void mergeTableSwitch(IntQueue queue, int pos, CodeIterator iter, Frame frame) throws BadBytecode { + // Skip 4 byte alignment padding + int index = (pos & ~3) + 4; + // default + merge(queue, frame, pos + iter.s32bitAt(index)); + int low = iter.s32bitAt(index += 4); + int high = iter.s32bitAt(index += 4); + int end = (high - low + 1) * 4 + (index += 4); + + // Offset table + for (; index < end; index += 4) { + int target = iter.s32bitAt(index) + pos; + merge(queue, frame, target); + } + } + + private Type zeroExtend(Type type) { + if (type == Type.SHORT || type == Type.BYTE || type == Type.CHAR || type == Type.BOOLEAN) + return Type.INTEGER; + + return type; + } +} diff --git a/src/main/javassist/bytecode/analysis/Executor.java b/src/main/javassist/bytecode/analysis/Executor.java new file mode 100644 index 00000000..7cf9a679 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/Executor.java @@ -0,0 +1,1012 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.NotFoundException; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import javassist.bytecode.MethodInfo; +import javassist.bytecode.Opcode; + +/** + * Executor is responsible for modeling the effects of a JVM instruction on a frame. + * + * @author Jason T. Greene + */ +public class Executor implements Opcode { + private final ConstPool constPool; + private final ClassPool classPool; + private final Type STRING_TYPE; + private final Type CLASS_TYPE; + private final Type THROWABLE_TYPE; + private int lastPos; + + public Executor(ClassPool classPool, ConstPool constPool) { + this.constPool = constPool; + this.classPool = classPool; + + try { + STRING_TYPE = getType("java.lang.String"); + CLASS_TYPE = getType("java.lang.Class"); + THROWABLE_TYPE = getType("java.lang.Throwable"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + + /** + * Execute the instruction, modeling the effects on the specified frame and subroutine. + * If a subroutine is passed, the access flags will be modified if this instruction accesses + * the local variable table. + * + * @param method the method containing the instruction + * @param pos the position of the instruction in the method + * @param iter the code iterator used to find the instruction + * @param frame the frame to modify to represent the result of the instruction + * @param subroutine the optional subroutine this instruction belongs to. + * @throws BadBytecode if the bytecode violates the jvm spec + */ + public void execute(MethodInfo method, int pos, CodeIterator iter, Frame frame, Subroutine subroutine) throws BadBytecode { + this.lastPos = pos; + int opcode = iter.byteAt(pos); + + + // Declared opcode in order + switch (opcode) { + case NOP: + break; + case ACONST_NULL: + frame.push(Type.UNINIT); + break; + case ICONST_M1: + case ICONST_0: + case ICONST_1: + case ICONST_2: + case ICONST_3: + case ICONST_4: + case ICONST_5: + frame.push(Type.INTEGER); + break; + case LCONST_0: + case LCONST_1: + frame.push(Type.LONG); + frame.push(Type.TOP); + break; + case FCONST_0: + case FCONST_1: + case FCONST_2: + frame.push(Type.FLOAT); + break; + case DCONST_0: + case DCONST_1: + frame.push(Type.DOUBLE); + frame.push(Type.TOP); + break; + case BIPUSH: + case SIPUSH: + frame.push(Type.INTEGER); + break; + case LDC: + evalLDC(iter.byteAt(pos + 1), frame); + break; + case LDC_W : + case LDC2_W : + evalLDC(iter.u16bitAt(pos + 1), frame); + break; + case ILOAD: + evalLoad(Type.INTEGER, iter.byteAt(pos + 1), frame, subroutine); + break; + case LLOAD: + evalLoad(Type.LONG, iter.byteAt(pos + 1), frame, subroutine); + break; + case FLOAD: + evalLoad(Type.FLOAT, iter.byteAt(pos + 1), frame, subroutine); + break; + case DLOAD: + evalLoad(Type.DOUBLE, iter.byteAt(pos + 1), frame, subroutine); + break; + case ALOAD: + evalLoad(Type.OBJECT, iter.byteAt(pos + 1), frame, subroutine); + break; + case ILOAD_0: + case ILOAD_1: + case ILOAD_2: + case ILOAD_3: + evalLoad(Type.INTEGER, opcode - ILOAD_0, frame, subroutine); + break; + case LLOAD_0: + case LLOAD_1: + case LLOAD_2: + case LLOAD_3: + evalLoad(Type.LONG, opcode - LLOAD_0, frame, subroutine); + break; + case FLOAD_0: + case FLOAD_1: + case FLOAD_2: + case FLOAD_3: + evalLoad(Type.FLOAT, opcode - FLOAD_0, frame, subroutine); + break; + case DLOAD_0: + case DLOAD_1: + case DLOAD_2: + case DLOAD_3: + evalLoad(Type.DOUBLE, opcode - DLOAD_0, frame, subroutine); + break; + case ALOAD_0: + case ALOAD_1: + case ALOAD_2: + case ALOAD_3: + evalLoad(Type.OBJECT, opcode - ALOAD_0, frame, subroutine); + break; + case IALOAD: + evalArrayLoad(Type.INTEGER, frame); + break; + case LALOAD: + evalArrayLoad(Type.LONG, frame); + break; + case FALOAD: + evalArrayLoad(Type.FLOAT, frame); + break; + case DALOAD: + evalArrayLoad(Type.DOUBLE, frame); + break; + case AALOAD: + evalArrayLoad(Type.OBJECT, frame); + break; + case BALOAD: + case CALOAD: + case SALOAD: + evalArrayLoad(Type.INTEGER, frame); + break; + case ISTORE: + evalStore(Type.INTEGER, iter.byteAt(pos + 1), frame, subroutine); + break; + case LSTORE: + evalStore(Type.LONG, iter.byteAt(pos + 1), frame, subroutine); + break; + case FSTORE: + evalStore(Type.FLOAT, iter.byteAt(pos + 1), frame, subroutine); + break; + case DSTORE: + evalStore(Type.DOUBLE, iter.byteAt(pos + 1), frame, subroutine); + break; + case ASTORE: + evalStore(Type.OBJECT, iter.byteAt(pos + 1), frame, subroutine); + break; + case ISTORE_0: + case ISTORE_1: + case ISTORE_2: + case ISTORE_3: + evalStore(Type.INTEGER, opcode - ISTORE_0, frame, subroutine); + break; + case LSTORE_0: + case LSTORE_1: + case LSTORE_2: + case LSTORE_3: + evalStore(Type.LONG, opcode - LSTORE_0, frame, subroutine); + break; + case FSTORE_0: + case FSTORE_1: + case FSTORE_2: + case FSTORE_3: + evalStore(Type.FLOAT, opcode - FSTORE_0, frame, subroutine); + break; + case DSTORE_0: + case DSTORE_1: + case DSTORE_2: + case DSTORE_3: + evalStore(Type.DOUBLE, opcode - DSTORE_0, frame, subroutine); + break; + case ASTORE_0: + case ASTORE_1: + case ASTORE_2: + case ASTORE_3: + evalStore(Type.OBJECT, opcode - ASTORE_0, frame, subroutine); + break; + case IASTORE: + evalArrayStore(Type.INTEGER, frame); + break; + case LASTORE: + evalArrayStore(Type.LONG, frame); + break; + case FASTORE: + evalArrayStore(Type.FLOAT, frame); + break; + case DASTORE: + evalArrayStore(Type.DOUBLE, frame); + break; + case AASTORE: + evalArrayStore(Type.OBJECT, frame); + break; + case BASTORE: + case CASTORE: + case SASTORE: + evalArrayStore(Type.INTEGER, frame); + break; + case POP: + if (frame.pop() == Type.TOP) + throw new BadBytecode("POP can not be used with a category 2 value, pos = " + pos); + break; + case POP2: + frame.pop(); + frame.pop(); + break; + case DUP: { + Type type = frame.peek(); + if (type == Type.TOP) + throw new BadBytecode("DUP can not be used with a category 2 value, pos = " + pos); + + frame.push(frame.peek()); + break; + } + case DUP_X1: + case DUP_X2: { + Type type = frame.peek(); + if (type == Type.TOP) + throw new BadBytecode("DUP can not be used with a category 2 value, pos = " + pos); + int end = frame.getTopIndex(); + int insert = end - (opcode - DUP_X1) - 1; + frame.push(type); + + while (end > insert) { + frame.setStack(end, frame.getStack(end - 1)); + end--; + } + frame.setStack(insert, type); + break; + } + case DUP2: + frame.push(frame.getStack(frame.getTopIndex() - 1)); + frame.push(frame.getStack(frame.getTopIndex() - 1)); + break; + case DUP2_X1: + case DUP2_X2: { + int end = frame.getTopIndex(); + int insert = end - (opcode - DUP2_X1) - 1; + Type type1 = frame.getStack(frame.getTopIndex() - 1); + Type type2 = frame.peek(); + frame.push(type1); + frame.push(type2); + while (end > insert) { + frame.setStack(end, frame.getStack(end - 2)); + end--; + } + frame.setStack(insert, type2); + frame.setStack(insert - 1, type1); + break; + } + case SWAP: { + Type type1 = frame.pop(); + Type type2 = frame.pop(); + if (type1.getSize() == 2 || type2.getSize() == 2) + throw new BadBytecode("Swap can not be used with category 2 values, pos = " + pos); + frame.push(type1); + frame.push(type2); + break; + } + + // Math + case IADD: + evalBinaryMath(Type.INTEGER, frame); + break; + case LADD: + evalBinaryMath(Type.LONG, frame); + break; + case FADD: + evalBinaryMath(Type.FLOAT, frame); + break; + case DADD: + evalBinaryMath(Type.DOUBLE, frame); + break; + case ISUB: + evalBinaryMath(Type.INTEGER, frame); + break; + case LSUB: + evalBinaryMath(Type.LONG, frame); + break; + case FSUB: + evalBinaryMath(Type.FLOAT, frame); + break; + case DSUB: + evalBinaryMath(Type.DOUBLE, frame); + break; + case IMUL: + evalBinaryMath(Type.INTEGER, frame); + break; + case LMUL: + evalBinaryMath(Type.LONG, frame); + break; + case FMUL: + evalBinaryMath(Type.FLOAT, frame); + break; + case DMUL: + evalBinaryMath(Type.DOUBLE, frame); + break; + case IDIV: + evalBinaryMath(Type.INTEGER, frame); + break; + case LDIV: + evalBinaryMath(Type.LONG, frame); + break; + case FDIV: + evalBinaryMath(Type.FLOAT, frame); + break; + case DDIV: + evalBinaryMath(Type.DOUBLE, frame); + break; + case IREM: + evalBinaryMath(Type.INTEGER, frame); + break; + case LREM: + evalBinaryMath(Type.LONG, frame); + break; + case FREM: + evalBinaryMath(Type.FLOAT, frame); + break; + case DREM: + evalBinaryMath(Type.DOUBLE, frame); + break; + + // Unary + case INEG: + verifyAssignable(Type.INTEGER, simplePeek(frame)); + break; + case LNEG: + verifyAssignable(Type.LONG, simplePeek(frame)); + break; + case FNEG: + verifyAssignable(Type.FLOAT, simplePeek(frame)); + break; + case DNEG: + verifyAssignable(Type.DOUBLE, simplePeek(frame)); + break; + + // Shifts + case ISHL: + evalShift(Type.INTEGER, frame); + break; + case LSHL: + evalShift(Type.LONG, frame); + break; + case ISHR: + evalShift(Type.INTEGER, frame); + break; + case LSHR: + evalShift(Type.LONG, frame); + break; + case IUSHR: + evalShift(Type.INTEGER,frame); + break; + case LUSHR: + evalShift(Type.LONG, frame); + break; + + // Bitwise Math + case IAND: + evalBinaryMath(Type.INTEGER, frame); + break; + case LAND: + evalBinaryMath(Type.LONG, frame); + break; + case IOR: + evalBinaryMath(Type.INTEGER, frame); + break; + case LOR: + evalBinaryMath(Type.LONG, frame); + break; + case IXOR: + evalBinaryMath(Type.INTEGER, frame); + break; + case LXOR: + evalBinaryMath(Type.LONG, frame); + break; + + case IINC: { + int index = iter.byteAt(pos + 1); + verifyAssignable(Type.INTEGER, frame.getLocal(index)); + access(index, Type.INTEGER, subroutine); + break; + } + + // Conversion + case I2L: + verifyAssignable(Type.INTEGER, simplePop(frame)); + simplePush(Type.LONG, frame); + break; + case I2F: + verifyAssignable(Type.INTEGER, simplePop(frame)); + simplePush(Type.FLOAT, frame); + break; + case I2D: + verifyAssignable(Type.INTEGER, simplePop(frame)); + simplePush(Type.DOUBLE, frame); + break; + case L2I: + verifyAssignable(Type.LONG, simplePop(frame)); + simplePush(Type.INTEGER, frame); + break; + case L2F: + verifyAssignable(Type.LONG, simplePop(frame)); + simplePush(Type.FLOAT, frame); + break; + case L2D: + verifyAssignable(Type.LONG, simplePop(frame)); + simplePush(Type.DOUBLE, frame); + break; + case F2I: + verifyAssignable(Type.FLOAT, simplePop(frame)); + simplePush(Type.INTEGER, frame); + break; + case F2L: + verifyAssignable(Type.FLOAT, simplePop(frame)); + simplePush(Type.LONG, frame); + break; + case F2D: + verifyAssignable(Type.FLOAT, simplePop(frame)); + simplePush(Type.DOUBLE, frame); + break; + case D2I: + verifyAssignable(Type.DOUBLE, simplePop(frame)); + simplePush(Type.INTEGER, frame); + break; + case D2L: + verifyAssignable(Type.DOUBLE, simplePop(frame)); + simplePush(Type.LONG, frame); + break; + case D2F: + verifyAssignable(Type.DOUBLE, simplePop(frame)); + simplePush(Type.FLOAT, frame); + break; + case I2B: + case I2C: + case I2S: + verifyAssignable(Type.INTEGER, frame.peek()); + break; + case LCMP: + verifyAssignable(Type.LONG, simplePop(frame)); + verifyAssignable(Type.LONG, simplePop(frame)); + frame.push(Type.INTEGER); + break; + case FCMPL: + case FCMPG: + verifyAssignable(Type.FLOAT, simplePop(frame)); + verifyAssignable(Type.FLOAT, simplePop(frame)); + frame.push(Type.INTEGER); + break; + case DCMPL: + case DCMPG: + verifyAssignable(Type.DOUBLE, simplePop(frame)); + verifyAssignable(Type.DOUBLE, simplePop(frame)); + frame.push(Type.INTEGER); + break; + + // Control flow + case IFEQ: + case IFNE: + case IFLT: + case IFGE: + case IFGT: + case IFLE: + verifyAssignable(Type.INTEGER, simplePop(frame)); + break; + case IF_ICMPEQ: + case IF_ICMPNE: + case IF_ICMPLT: + case IF_ICMPGE: + case IF_ICMPGT: + case IF_ICMPLE: + verifyAssignable(Type.INTEGER, simplePop(frame)); + verifyAssignable(Type.INTEGER, simplePop(frame)); + break; + case IF_ACMPEQ: + case IF_ACMPNE: + verifyAssignable(Type.OBJECT, simplePop(frame)); + verifyAssignable(Type.OBJECT, simplePop(frame)); + break; + case GOTO: + break; + case JSR: + frame.push(Type.RETURN_ADDRESS); + break; + case RET: + verifyAssignable(Type.RETURN_ADDRESS, frame.getLocal(iter.byteAt(pos + 1))); + break; + case TABLESWITCH: + case LOOKUPSWITCH: + case IRETURN: + verifyAssignable(Type.INTEGER, simplePop(frame)); + break; + case LRETURN: + verifyAssignable(Type.LONG, simplePop(frame)); + break; + case FRETURN: + verifyAssignable(Type.FLOAT, simplePop(frame)); + break; + case DRETURN: + verifyAssignable(Type.DOUBLE, simplePop(frame)); + break; + case ARETURN: + try { + CtClass returnType = Descriptor.getReturnType(method.getDescriptor(), classPool); + verifyAssignable(Type.get(returnType), simplePop(frame)); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + break; + case RETURN: + break; + case GETSTATIC: + evalGetField(opcode, iter.u16bitAt(pos + 1), frame); + break; + case PUTSTATIC: + evalPutField(opcode, iter.u16bitAt(pos + 1), frame); + break; + case GETFIELD: + evalGetField(opcode, iter.u16bitAt(pos + 1), frame); + break; + case PUTFIELD: + evalPutField(opcode, iter.u16bitAt(pos + 1), frame); + break; + case INVOKEVIRTUAL: + case INVOKESPECIAL: + case INVOKESTATIC: + evalInvokeMethod(opcode, iter.u16bitAt(pos + 1), frame); + break; + case INVOKEINTERFACE: + evalInvokeIntfMethod(opcode, iter.u16bitAt(pos + 1), frame); + break; + case 186: + throw new RuntimeException("Bad opcode 186"); + case NEW: + frame.push(typeFromDesc(constPool.getClassInfo(iter.u16bitAt(pos + 1)))); + break; + case NEWARRAY: + evalNewArray(pos, iter, frame); + break; + case ANEWARRAY: + evalNewObjectArray(pos, iter, frame); + break; + case ARRAYLENGTH: { + Type array = simplePop(frame); + if (! array.isArray() && array != Type.UNINIT) + throw new BadBytecode("Array length passed a non-array [pos = " + pos + "]: " + array); + frame.push(Type.INTEGER); + break; + } + case ATHROW: + verifyAssignable(THROWABLE_TYPE, simplePop(frame)); + break; + case CHECKCAST: + verifyAssignable(Type.OBJECT, simplePop(frame)); + frame.push(typeFromDesc(constPool.getClassInfo(iter.u16bitAt(pos + 1)))); + break; + case INSTANCEOF: + verifyAssignable(Type.OBJECT, simplePop(frame)); + frame.push(Type.INTEGER); + break; + case MONITORENTER: + case MONITOREXIT: + verifyAssignable(Type.OBJECT, simplePop(frame)); + break; + case WIDE: + evalWide(pos, iter, frame, subroutine); + break; + case MULTIANEWARRAY: + evalNewObjectArray(pos, iter, frame); + break; + case IFNULL: + case IFNONNULL: + verifyAssignable(Type.OBJECT, simplePop(frame)); + break; + case GOTO_W: + break; + case JSR_W: + frame.push(Type.RETURN_ADDRESS); + break; + } + } + + private Type zeroExtend(Type type) { + if (type == Type.SHORT || type == Type.BYTE || type == Type.CHAR || type == Type.BOOLEAN) + return Type.INTEGER; + + return type; + } + + private void evalArrayLoad(Type expectedComponent, Frame frame) throws BadBytecode { + Type index = frame.pop(); + Type array = frame.pop(); + + // Special case, an array defined by aconst_null + // TODO - we might need to be more inteligent about this + if (array == Type.UNINIT) { + verifyAssignable(Type.INTEGER, index); + if (expectedComponent == Type.OBJECT) { + simplePush(Type.UNINIT, frame); + } else { + simplePush(expectedComponent, frame); + } + return; + } + + Type component = array.getComponent(); + + if (component == null) + throw new BadBytecode("Not an array! [pos = " + lastPos + "]: " + component); + + component = zeroExtend(component); + + verifyAssignable(expectedComponent, component); + verifyAssignable(Type.INTEGER, index); + simplePush(component, frame); + } + + private void evalArrayStore(Type expectedComponent, Frame frame) throws BadBytecode { + Type value = simplePop(frame); + Type index = frame.pop(); + Type array = frame.pop(); + + if (array == Type.UNINIT) { + verifyAssignable(Type.INTEGER, index); + return; + } + + Type component = array.getComponent(); + + if (component == null) + throw new BadBytecode("Not an array! [pos = " + lastPos + "]: " + component); + + component = zeroExtend(component); + + verifyAssignable(expectedComponent, component); + verifyAssignable(Type.INTEGER, index); + + // This intentionally only checks for Object on aastore + // downconverting of an array (no casts) + // e.g. Object[] blah = new String[]; + // blah[2] = (Object) "test"; + // blah[3] = new Integer(); // compiler doesnt catch it (has legal bytecode), + // // but will throw arraystoreexception + if (expectedComponent == Type.OBJECT) { + verifyAssignable(expectedComponent, value); + } else { + verifyAssignable(component, value); + } + } + + private void evalBinaryMath(Type expected, Frame frame) throws BadBytecode { + Type value2 = simplePop(frame); + Type value1 = simplePop(frame); + + verifyAssignable(expected, value2); + verifyAssignable(expected, value1); + simplePush(value1, frame); + } + + private void evalGetField(int opcode, int index, Frame frame) throws BadBytecode { + String desc = constPool.getFieldrefType(index); + Type type = zeroExtend(typeFromDesc(desc)); + + if (opcode == GETFIELD) { + Type objectType = typeFromDesc(constPool.getFieldrefClassName(index)); + verifyAssignable(objectType, simplePop(frame)); + } + + simplePush(type, frame); + } + + private void evalInvokeIntfMethod(int opcode, int index, Frame frame) throws BadBytecode { + String desc = constPool.getInterfaceMethodrefType(index); + Type[] types = paramTypesFromDesc(desc); + int i = types.length; + + while (i > 0) + verifyAssignable(zeroExtend(types[--i]), simplePop(frame)); + + String classDesc = constPool.getInterfaceMethodrefClassName(index); + Type objectType = typeFromDesc(classDesc); + verifyAssignable(objectType, simplePop(frame)); + + Type returnType = returnTypeFromDesc(desc); + if (returnType != Type.VOID) + simplePush(zeroExtend(returnType), frame); + } + + private void evalInvokeMethod(int opcode, int index, Frame frame) throws BadBytecode { + String desc = constPool.getMethodrefType(index); + Type[] types = paramTypesFromDesc(desc); + int i = types.length; + + while (i > 0) + verifyAssignable(zeroExtend(types[--i]), simplePop(frame)); + + if (opcode != INVOKESTATIC) { + Type objectType = typeFromDesc(constPool.getMethodrefClassName(index)); + verifyAssignable(objectType, simplePop(frame)); + } + + Type returnType = returnTypeFromDesc(desc); + if (returnType != Type.VOID) + simplePush(zeroExtend(returnType), frame); + } + + + private void evalLDC(int index, Frame frame) throws BadBytecode { + int tag = constPool.getTag(index); + Type type; + switch (tag) { + case ConstPool.CONST_String: + type = STRING_TYPE; + break; + case ConstPool.CONST_Integer: + type = Type.INTEGER; + break; + case ConstPool.CONST_Float: + type = Type.FLOAT; + break; + case ConstPool.CONST_Long: + type = Type.LONG; + break; + case ConstPool.CONST_Double: + type = Type.DOUBLE; + break; + case ConstPool.CONST_Class: + type = CLASS_TYPE; + break; + default: + throw new BadBytecode("bad LDC [pos = " + lastPos + "]: " + tag); + } + + simplePush(type, frame); + } + + private void evalLoad(Type expected, int index, Frame frame, Subroutine subroutine) throws BadBytecode { + Type type = frame.getLocal(index); + + verifyAssignable(expected, type); + + simplePush(type, frame); + access(index, type, subroutine); + } + + private void evalNewArray(int pos, CodeIterator iter, Frame frame) throws BadBytecode { + verifyAssignable(Type.INTEGER, simplePop(frame)); + Type type = null; + int typeInfo = iter.byteAt(pos + 1); + switch (typeInfo) { + case T_BOOLEAN: + type = getType("boolean[]"); + break; + case T_CHAR: + type = getType("char[]"); + break; + case T_BYTE: + type = getType("byte[]"); + break; + case T_SHORT: + type = getType("short[]"); + break; + case T_INT: + type = getType("int[]"); + break; + case T_LONG: + type = getType("long[]"); + break; + case T_FLOAT: + type = getType("float[]"); + break; + case T_DOUBLE: + type = getType("double[]"); + break; + default: + throw new BadBytecode("Invalid array type [pos = " + pos + "]: " + typeInfo); + + } + + frame.push(type); + } + + private void evalNewObjectArray(int pos, CodeIterator iter, Frame frame) throws BadBytecode { + // Convert to x[] format + Type type = typeFromDesc(constPool.getClassInfo(iter.u16bitAt(pos + 1))); + String name = type.getCtClass().getName(); + int opcode = iter.byteAt(pos); + int dimensions; + + if (opcode == MULTIANEWARRAY) { + dimensions = iter.byteAt(pos + 3); + } else { + name = name + "[]"; + dimensions = 1; + } + + while (dimensions-- > 0) { + verifyAssignable(Type.INTEGER, simplePop(frame)); + } + + simplePush(getType(name), frame); + } + + private void evalPutField(int opcode, int index, Frame frame) throws BadBytecode { + String desc = constPool.getFieldrefType(index); + Type type = zeroExtend(typeFromDesc(desc)); + + verifyAssignable(type, simplePop(frame)); + + if (opcode == PUTFIELD) { + Type objectType = typeFromDesc(constPool.getFieldrefClassName(index)); + verifyAssignable(objectType, simplePop(frame)); + } + } + + private void evalShift(Type expected, Frame frame) throws BadBytecode { + Type value2 = simplePop(frame); + Type value1 = simplePop(frame); + + verifyAssignable(Type.INTEGER, value2); + verifyAssignable(expected, value1); + simplePush(value1, frame); + } + + private void evalStore(Type expected, int index, Frame frame, Subroutine subroutine) throws BadBytecode { + Type type = simplePop(frame); + + // RETURN_ADDRESS is allowed by ASTORE + if (! (expected == Type.OBJECT && type == Type.RETURN_ADDRESS)) + verifyAssignable(expected, type); + simpleSetLocal(index, type, frame); + access(index, type, subroutine); + } + + private void evalWide(int pos, CodeIterator iter, Frame frame, Subroutine subroutine) throws BadBytecode { + int opcode = iter.byteAt(pos + 1); + int index = iter.u16bitAt(pos + 2); + switch (opcode) { + case ILOAD: + evalLoad(Type.INTEGER, index, frame, subroutine); + break; + case LLOAD: + evalLoad(Type.LONG, index, frame, subroutine); + break; + case FLOAD: + evalLoad(Type.FLOAT, index, frame, subroutine); + break; + case DLOAD: + evalLoad(Type.DOUBLE, index, frame, subroutine); + break; + case ALOAD: + evalLoad(Type.OBJECT, index, frame, subroutine); + break; + case ISTORE: + evalStore(Type.INTEGER, index, frame, subroutine); + break; + case LSTORE: + evalStore(Type.LONG, index, frame, subroutine); + break; + case FSTORE: + evalStore(Type.FLOAT, index, frame, subroutine); + break; + case DSTORE: + evalStore(Type.DOUBLE, index, frame, subroutine); + break; + case ASTORE: + evalStore(Type.OBJECT, index, frame, subroutine); + break; + case IINC: + verifyAssignable(Type.INTEGER, frame.getLocal(index)); + break; + case RET: + verifyAssignable(Type.RETURN_ADDRESS, frame.getLocal(index)); + break; + default: + throw new BadBytecode("Invalid WIDE operand [pos = " + pos + "]: " + opcode); + } + + } + + private Type getType(String name) throws BadBytecode { + try { + return Type.get(classPool.get(name)); + } catch (NotFoundException e) { + throw new BadBytecode("Could not find class [pos = " + lastPos + "]: " + name); + } + } + + private Type[] paramTypesFromDesc(String desc) throws BadBytecode { + CtClass classes[] = null; + try { + classes = Descriptor.getParameterTypes(desc, classPool); + } catch (NotFoundException e) { + throw new BadBytecode("Could not find class in descriptor [pos = " + lastPos + "]: " + e.getMessage()); + } + + if (classes == null) + throw new BadBytecode("Could not obtain parameters for descriptor [pos = " + lastPos + "]: " + desc); + + Type[] types = new Type[classes.length]; + for (int i = 0; i < types.length; i++) + types[i] = Type.get(classes[i]); + + return types; + } + + private Type returnTypeFromDesc(String desc) throws BadBytecode { + CtClass clazz = null; + try { + clazz = Descriptor.getReturnType(desc, classPool); + } catch (NotFoundException e) { + throw new BadBytecode("Could not find class in descriptor [pos = " + lastPos + "]: " + e.getMessage()); + } + + if (clazz == null) + throw new BadBytecode("Could not obtain return type for descriptor [pos = " + lastPos + "]: " + desc); + + return Type.get(clazz); + } + + private Type simplePeek(Frame frame) { + Type type = frame.peek(); + return (type == Type.TOP) ? frame.getStack(frame.getTopIndex() - 1) : type; + } + + private Type simplePop(Frame frame) { + Type type = frame.pop(); + return (type == Type.TOP) ? frame.pop() : type; + } + + private void simplePush(Type type, Frame frame) { + frame.push(type); + if (type.getSize() == 2) + frame.push(Type.TOP); + } + + private void access(int index, Type type, Subroutine subroutine) { + if (subroutine == null) + return; + subroutine.access(index); + if (type.getSize() == 2) + subroutine.access(index + 1); + } + + private void simpleSetLocal(int index, Type type, Frame frame) { + frame.setLocal(index, type); + if (type.getSize() == 2) + frame.setLocal(index + 1, Type.TOP); + } + + private Type typeFromDesc(String desc) throws BadBytecode { + CtClass clazz = null; + try { + clazz = Descriptor.toCtClass(desc, classPool); + } catch (NotFoundException e) { + throw new BadBytecode("Could not find class in descriptor [pos = " + lastPos + "]: " + e.getMessage()); + } + + if (clazz == null) + throw new BadBytecode("Could not obtain type for descriptor [pos = " + lastPos + "]: " + desc); + + return Type.get(clazz); + } + + private void verifyAssignable(Type expected, Type type) throws BadBytecode { + if (! expected.isAssignableFrom(type)) + throw new BadBytecode("Expected type: " + expected + " Got: " + type + " [pos = " + lastPos + "]"); + } +} diff --git a/src/main/javassist/bytecode/analysis/Frame.java b/src/main/javassist/bytecode/analysis/Frame.java new file mode 100644 index 00000000..cf646f4b --- /dev/null +++ b/src/main/javassist/bytecode/analysis/Frame.java @@ -0,0 +1,288 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + + +/** + * Represents the stack frame and local variable table at a particular point in time. + * + * @author Jason T. Greene + */ +public class Frame { + private Type[] locals; + private Type[] stack; + private int top; + private boolean jsrMerged; + private boolean retMerged; + + /** + * Create a new frame with the specified local variable table size, and max stack size + * + * @param locals the number of local variable table entries + * @param stack the maximum stack size + */ + public Frame(int locals, int stack) { + this.locals = new Type[locals]; + this.stack = new Type[stack]; + } + + /** + * Returns the local varaible table entry at index. + * + * @param index the position in the table + * @return the type if one exists, or null if the position is empty + */ + public Type getLocal(int index) { + return locals[index]; + } + + /** + * Sets the local variable table entry at index to a type. + * + * @param index the position in the table + * @param type the type to set at the position + */ + public void setLocal(int index, Type type) { + locals[index] = type; + } + + + /** + * Returns the type on the stack at the specified index. + * + * @param index the position on the stack + * @return the type of the stack position + */ + public Type getStack(int index) { + return stack[index]; + } + + /** + * Sets the type of the stack position + * + * @param index the position on the stack + * @param type the type to set + */ + public void setStack(int index, Type type) { + stack[index] = type; + } + + /** + * Empties the stack + */ + public void clearStack() { + top = 0; + } + + /** + * Gets the index of the type sitting at the top of the stack. + * This is not to be confused with a length operation which + * would return the number of elements, not the position of + * the last element. + * + * @return the position of the element at the top of the stack + */ + public int getTopIndex() { + return top - 1; + } + + /** + * Returns the number of local variable table entries, specified + * at construction. + * + * @return the number of local variable table entries + */ + public int localsLength() { + return locals.length; + } + + /** + * Gets the top of the stack without altering it + * + * @return the top of the stack + */ + public Type peek() { + if (top < 1) + throw new IndexOutOfBoundsException("Stack is empty"); + + return stack[top - 1]; + } + + /** + * Alters the stack to contain one less element and return it. + * + * @return the element popped from the stack + */ + public Type pop() { + if (top < 1) + throw new IndexOutOfBoundsException("Stack is empty"); + return stack[--top]; + } + + /** + * Alters the stack by placing the passed type on the top + * + * @param type the type to add to the top + */ + public void push(Type type) { + stack[top++] = type; + } + + + /** + * Makes a shallow copy of this frame, i.e. the type instances will + * remain the same. + * + * @return the shallow copy + */ + public Frame copy() { + Frame frame = new Frame(locals.length, stack.length); + System.arraycopy(locals, 0, frame.locals, 0, locals.length); + System.arraycopy(stack, 0, frame.stack, 0, stack.length); + frame.top = top; + return frame; + } + + /** + * Makes a shallow copy of the stack portion of this frame. The local + * variable table size will be copied, but its contents will be empty. + * + * @return the shallow copy of the stack + */ + public Frame copyStack() { + Frame frame = new Frame(locals.length, stack.length); + System.arraycopy(stack, 0, frame.stack, 0, stack.length); + frame.top = top; + return frame; + } + + /** + * Merges all types on the stack of this frame instance with that of the specified frame. + * The local variable table is left untouched. + * + * @param frame the frame to merge the stack from + * @return true if any changes where made + */ + public boolean mergeStack(Frame frame) { + boolean changed = false; + if (top != frame.top) + throw new RuntimeException("Operand stacks could not be merged, they are different sizes!"); + + for (int i = 0; i < top; i++) { + if (stack[i] != null) { + Type prev = stack[i]; + Type merged = prev.merge(frame.stack[i]); + if (merged == Type.BOGUS) + throw new RuntimeException("Operand stacks could not be merged due to differing primitive types: pos = " + i); + + stack[i] = merged; + // always replace the instance in case a multi-interface type changes to a normal Type + if ((! merged.equals(prev)) || merged.popChanged()) { + changed = true; + } + } + } + + return changed; + } + + /** + * Merges all types on the stack and local variable table of this frame with that of the specified + * type. + * + * @param frame the frame to merge with + * @return true if any changes to this frame where made by this merge + */ + public boolean merge(Frame frame) { + boolean changed = false; + + // Local variable table + for (int i = 0; i < locals.length; i++) { + if (locals[i] != null) { + Type prev = locals[i]; + Type merged = prev.merge(frame.locals[i]); + // always replace the instance in case a multi-interface type changes to a normal Type + locals[i] = merged; + if (! merged.equals(prev) || merged.popChanged()) { + changed = true; + } + } else if (frame.locals[i] != null) { + locals[i] = frame.locals[i]; + changed = true; + } + } + + changed |= mergeStack(frame); + return changed; + } + + public String toString() { + StringBuffer buffer = new StringBuffer(); + + buffer.append("locals = ["); + for (int i = 0; i < locals.length; i++) { + buffer.append(locals[i] == null ? "empty" : locals[i].toString()); + if (i < locals.length - 1) + buffer.append(", "); + } + buffer.append("] stack = ["); + for (int i = 0; i < top; i++) { + buffer.append(stack[i]); + if (i < top - 1) + buffer.append(", "); + } + buffer.append("]"); + + return buffer.toString(); + } + + /** + * Whether or not state from the source JSR instruction has been merged + * + * @return true if JSR state has been merged + */ + boolean isJsrMerged() { + return jsrMerged; + } + + /** + * Sets whether of not the state from the source JSR instruction has been merged + * + * @param jsrMerged true if merged, otherwise false + */ + void setJsrMerged(boolean jsrMerged) { + this.jsrMerged = jsrMerged; + } + + /** + * Whether or not state from the RET instruction, of the subroutine that was jumped + * to has been merged. + * + * @return true if RET state has been merged + */ + boolean isRetMerged() { + return retMerged; + } + + /** + * Sets whether or not state from the RET instruction, of the subroutine that was jumped + * to has been merged. + * + * @param retMerged true if RET state has been merged + */ + void setRetMerged(boolean retMerged) { + this.retMerged = retMerged; + } +} diff --git a/src/main/javassist/bytecode/analysis/FramePrinter.java b/src/main/javassist/bytecode/analysis/FramePrinter.java new file mode 100644 index 00000000..263e5299 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/FramePrinter.java @@ -0,0 +1,136 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + +import java.io.PrintStream; + +import javassist.CtClass; +import javassist.CtMethod; +import javassist.Modifier; +import javassist.NotFoundException; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.CodeAttribute; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import javassist.bytecode.InstructionPrinter; +import javassist.bytecode.MethodInfo; + +/** + * A utility class for printing a merged view of the frame state and the + * instructions of a method. + * + * @author Jason T. Greene + */ +public final class FramePrinter { + private final PrintStream stream; + + public FramePrinter(PrintStream stream) { + this.stream = stream; + } + + public static void print(CtClass clazz, PrintStream stream) { + (new FramePrinter(stream)).print(clazz); + } + + public void print(CtClass clazz) { + CtMethod[] methods = clazz.getDeclaredMethods(); + for (int i = 0; i < methods.length; i++) { + print(methods[i]); + } + } + + private String getMethodString(CtMethod method) { + try { + return Modifier.toString(method.getModifiers()) + " " + + method.getReturnType().getName() + " " + method.getName() + + Descriptor.toString(method.getSignature()) + ";"; + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + } + + public void print(CtMethod method) { + stream.println("\n" + getMethodString(method)); + MethodInfo info = method.getMethodInfo2(); + ConstPool pool = info.getConstPool(); + CodeAttribute code = info.getCodeAttribute(); + if (code == null) + return; + + Frame[] frames; + try { + frames = (new Analyzer()).analyze(method.getDeclaringClass(), info); + } catch (BadBytecode e) { + throw new RuntimeException(e); + } + + int spacing = String.valueOf(code.getCodeLength()).length(); + + CodeIterator iterator = code.iterator(); + while (iterator.hasNext()) { + int pos; + try { + pos = iterator.next(); + } catch (BadBytecode e) { + throw new RuntimeException(e); + } + + stream.println(pos + ": " + InstructionPrinter.instructionString(iterator, pos, pool)); + + addSpacing(spacing + 3); + Frame frame = frames[pos]; + if (frame == null) { + stream.println("--DEAD CODE--"); + continue; + } + printStack(frame); + + addSpacing(spacing + 3); + printLocals(frame); + } + + } + + private void printStack(Frame frame) { + stream.print("stack ["); + int top = frame.getTopIndex(); + for (int i = 0; i <= top; i++) { + if (i > 0) + stream.print(", "); + Type type = frame.getStack(i); + stream.print(type); + } + stream.println("]"); + } + + private void printLocals(Frame frame) { + stream.print("locals ["); + int length = frame.localsLength(); + for (int i = 0; i < length; i++) { + if (i > 0) + stream.print(", "); + Type type = frame.getLocal(i); + stream.print(type == null ? "empty" : type.toString()); + } + stream.println("]"); + } + + private void addSpacing(int count) { + while (count-- > 0) + stream.print(' '); + } + +} diff --git a/src/main/javassist/bytecode/analysis/IntQueue.java b/src/main/javassist/bytecode/analysis/IntQueue.java new file mode 100644 index 00000000..d50cddc3 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/IntQueue.java @@ -0,0 +1,56 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + +import java.util.NoSuchElementException; + +class IntQueue { + private static class Entry { + private IntQueue.Entry next; + private int value; + private Entry(int value) { + this.value = value; + } + } + private IntQueue.Entry head; + + private IntQueue.Entry tail; + + void add(int value) { + IntQueue.Entry entry = new Entry(value); + if (tail != null) + tail.next = entry; + tail = entry; + + if (head == null) + head = entry; + } + + boolean isEmpty() { + return head == null; + } + + int take() { + if (head == null) + throw new NoSuchElementException(); + + int value = head.value; + head = head.next; + if (head == null) + tail = null; + + return value; + } +}
\ No newline at end of file diff --git a/src/main/javassist/bytecode/analysis/MultiArrayType.java b/src/main/javassist/bytecode/analysis/MultiArrayType.java new file mode 100644 index 00000000..750116c6 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/MultiArrayType.java @@ -0,0 +1,129 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.NotFoundException; + +/** + * Represents an array of {@link MultiType} instances. + * + * @author Jason T. Greene + */ +public class MultiArrayType extends Type { + private MultiType component; + private int dims; + + public MultiArrayType(MultiType component, int dims) { + super(null); + this.component = component; + this.dims = dims; + } + + public CtClass getCtClass() { + CtClass clazz = component.getCtClass(); + if (clazz == null) + return null; + + ClassPool pool = clazz.getClassPool(); + if (pool == null) + pool = ClassPool.getDefault(); + + String name = arrayName(clazz.getName(), dims); + + try { + return pool.get(name); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + } + + boolean popChanged() { + return component.popChanged(); + } + + public int getDimensions() { + return dims; + } + + public Type getComponent() { + return dims == 1 ? (Type)component : new MultiArrayType(component, dims - 1); + } + + public int getSize() { + return 1; + } + + public boolean isArray() { + return true; + } + + public boolean isAssignableFrom(Type type) { + throw new UnsupportedOperationException("Not implemented"); + } + + public boolean isReference() { + return true; + } + + public boolean isAssignableTo(Type type) { + if (eq(type.getCtClass(), Type.OBJECT.getCtClass())) + return true; + + if (eq(type.getCtClass(), Type.CLONEABLE.getCtClass())) + return true; + + if (eq(type.getCtClass(), Type.SERIALIZABLE.getCtClass())) + return true; + + if (! type.isArray()) + return false; + + Type typeRoot = getRootComponent(type); + int typeDims = type.getDimensions(); + + if (typeDims > dims) + return false; + + if (typeDims < dims) { + if (eq(typeRoot.getCtClass(), Type.OBJECT.getCtClass())) + return true; + + if (eq(typeRoot.getCtClass(), Type.CLONEABLE.getCtClass())) + return true; + + if (eq(typeRoot.getCtClass(), Type.SERIALIZABLE.getCtClass())) + return true; + + return false; + } + + return component.isAssignableTo(typeRoot); + } + + public boolean equals(Object o) { + if (! (o instanceof MultiArrayType)) + return false; + MultiArrayType multi = (MultiArrayType)o; + + return component.equals(multi.component) && dims == multi.dims; + } + + public String toString() { + // follows the same detailed formating scheme as component + return arrayName(component.toString(), dims); + } +} diff --git a/src/main/javassist/bytecode/analysis/MultiType.java b/src/main/javassist/bytecode/analysis/MultiType.java new file mode 100644 index 00000000..31486ea4 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/MultiType.java @@ -0,0 +1,315 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javassist.CtClass; + +/** + * MultiType represents an unresolved type. Whenever two <literal>Type</literal> + * instances are merged, if they share more than one super type (either an + * interface or a superclass), then a <literal>MultiType</literal> is used to + * represent the possible super types. The goal of a <literal>MultiType</literal> + * is to reduce the set of possible types down to a single resolved type. This + * is done by eliminating non-assignable types from the typeset when the + * <literal>MultiType</literal> is passed as an argument to + * {@link Type#isAssignableFrom(Type)}, as well as removing non-intersecting + * types during a merge. + * + * Note: Currently the <litera>MultiType</literal> instance is reused as much + * as possible so that updates are visible from all frames. In addition, all + * <literal>MultiType</literal> merge paths are also updated. This is somewhat + * hackish, but it appears to handle most scenarios. + * + * @author Jason T. Greene + */ + +/* TODO - A better, but more involved, approach would be to track the instruction + * offset that resulted in the creation of this type, and + * whenever the typeset changes, to force a merge on that position. This + * would require creating a new MultiType instance every time the typeset + * changes, and somehow communicating assignment changes to the Analyzer + */ +public class MultiType extends Type { + private Map interfaces; + private Type resolved; + private Type potentialClass; + private MultiType mergeSource; + private boolean changed = false; + + public MultiType(Map interfaces) { + this(interfaces, null); + } + + public MultiType(Map interfaces, Type potentialClass) { + super(null); + this.interfaces = interfaces; + this.potentialClass = potentialClass; + } + + /** + * Gets the class that corresponds with this type. If this information + * is not yet known, java.lang.Object will be returned. + */ + public CtClass getCtClass() { + if (resolved != null) + return resolved.getCtClass(); + + return Type.OBJECT.getCtClass(); + } + + /** + * Always returns null since this type is never used for an array. + */ + public Type getComponent() { + return null; + } + + /** + * Always returns 1, since this type is a reference. + */ + public int getSize() { + return 1; + } + + /** + * Always reutnrs false since this type is never used for an array + */ + public boolean isArray() { + return false; + } + + /** + * Returns true if the internal state has changed. + */ + boolean popChanged() { + boolean changed = this.changed; + this.changed = false; + return changed; + } + + public boolean isAssignableFrom(Type type) { + throw new UnsupportedOperationException("Not implemented"); + } + + public boolean isAssignableTo(Type type) { + if (resolved != null) + return type.isAssignableFrom(resolved); + + if (Type.OBJECT.equals(type)) + return true; + + if (potentialClass != null && !type.isAssignableFrom(potentialClass)) + potentialClass = null; + + Map map = mergeMultiAndSingle(this, type); + + if (map.size() == 1 && potentialClass == null) { + // Update previous merge paths to the same resolved type + resolved = Type.get((CtClass)map.values().iterator().next()); + propogateResolved(); + + return true; + } + + // Keep all previous merge paths up to date + if (map.size() >= 1) { + interfaces = map; + propogateState(); + + return true; + } + + if (potentialClass != null) { + resolved = potentialClass; + propogateResolved(); + + return true; + } + + return false; + } + + private void propogateState() { + MultiType source = mergeSource; + while (source != null) { + source.interfaces = interfaces; + source.potentialClass = potentialClass; + source = source.mergeSource; + } + } + + private void propogateResolved() { + MultiType source = mergeSource; + while (source != null) { + source.resolved = resolved; + source = source.mergeSource; + } + } + + /** + * Always returns true, since this type is always a reference. + * + * @return true + */ + public boolean isReference() { + return true; + } + + private Map getAllMultiInterfaces(MultiType type) { + Map map = new HashMap(); + + Iterator iter = type.interfaces.values().iterator(); + while (iter.hasNext()) { + CtClass intf = (CtClass)iter.next(); + map.put(intf.getName(), intf); + getAllInterfaces(intf, map); + } + + return map; + } + + + private Map mergeMultiInterfaces(MultiType type1, MultiType type2) { + Map map1 = getAllMultiInterfaces(type1); + Map map2 = getAllMultiInterfaces(type2); + + return findCommonInterfaces(map1, map2); + } + + private Map mergeMultiAndSingle(MultiType multi, Type single) { + Map map1 = getAllMultiInterfaces(multi); + Map map2 = getAllInterfaces(single.getCtClass(), null); + + return findCommonInterfaces(map1, map2); + } + + private boolean inMergeSource(MultiType source) { + while (source != null) { + if (source == this) { + System.out.println("INMERGESOURCE!"); + return true; + + } + source = source.mergeSource; + } + + return false; + } + + public Type merge(Type type) { + if (this == type) + return this; + + if (type == UNINIT) + return this; + + if (type == BOGUS) + return BOGUS; + + if (type == null) + return this; + + if (resolved != null) + return resolved.merge(type); + + if (potentialClass != null) { + Type mergePotential = potentialClass.merge(type); + if (! mergePotential.equals(potentialClass) || mergePotential.popChanged()) { + potentialClass = Type.OBJECT.equals(mergePotential) ? null : mergePotential; + changed = true; + } + } + + Map merged; + + if (type instanceof MultiType) { + MultiType multi = (MultiType)type; + + if (multi.resolved != null) { + merged = mergeMultiAndSingle(this, multi.resolved); + } else { + merged = mergeMultiInterfaces(multi, this); + if (! inMergeSource(multi)) + mergeSource = multi; + } + } else { + merged = mergeMultiAndSingle(this, type); + } + + // Keep all previous merge paths up to date + if (merged.size() > 1 || (merged.size() == 1 && potentialClass != null)) { + // Check for changes + if (merged.size() != interfaces.size()) { + changed = true; + } else if (changed == false){ + Iterator iter = merged.keySet().iterator(); + while (iter.hasNext()) + if (! interfaces.containsKey(iter.next())) + changed = true; + } + + interfaces = merged; + propogateState(); + + return this; + } + + if (merged.size() == 1) { + resolved = Type.get((CtClass) merged.values().iterator().next()); + } else if (potentialClass != null){ + resolved = potentialClass; + } else { + resolved = OBJECT; + } + + propogateResolved(); + + return resolved; + } + + public boolean equals(Object o) { + if (! (o instanceof MultiType)) + return false; + + MultiType multi = (MultiType) o; + if (resolved != null) + return resolved.equals(multi.resolved); + else if (multi.resolved != null) + return false; + + return interfaces.keySet().equals(multi.interfaces.keySet()); + } + + public String toString() { + if (resolved != null) + return resolved.toString(); + + StringBuffer buffer = new StringBuffer("{"); + Iterator iter = interfaces.keySet().iterator(); + while (iter.hasNext()) { + buffer.append(iter.next()); + buffer.append(", "); + } + buffer.setLength(buffer.length() - 2); + if (potentialClass != null) + buffer.append(", *").append(potentialClass.toString()); + buffer.append("}"); + return buffer.toString(); + } +} diff --git a/src/main/javassist/bytecode/analysis/Subroutine.java b/src/main/javassist/bytecode/analysis/Subroutine.java new file mode 100644 index 00000000..924d37a6 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/Subroutine.java @@ -0,0 +1,66 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Represents a nested method subroutine (marked by JSR and RET). + * + * @author Jason T. Greene + */ +public class Subroutine { + //private Set callers = new HashSet(); + private List callers = new ArrayList(); + private Set access = new HashSet(); + private int start; + + public Subroutine(int start, int caller) { + this.start = start; + callers.add(Integer.valueOf(caller)); + } + + public void addCaller(int caller) { + callers.add(Integer.valueOf(caller)); + } + + public int start() { + return start; + } + + public void access(int index) { + access.add(Integer.valueOf(index)); + } + + public boolean isAccessed(int index) { + return access.contains(Integer.valueOf(index)); + } + + public Collection accessed() { + return access; + } + + public Collection callers() { + return callers; + } + + public String toString() { + return "start = " + start + " callers = " + callers.toString(); + } +}
\ No newline at end of file diff --git a/src/main/javassist/bytecode/analysis/SubroutineScanner.java b/src/main/javassist/bytecode/analysis/SubroutineScanner.java new file mode 100644 index 00000000..3feae539 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/SubroutineScanner.java @@ -0,0 +1,156 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javassist.bytecode.BadBytecode; +import javassist.bytecode.CodeAttribute; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.ExceptionTable; +import javassist.bytecode.MethodInfo; +import javassist.bytecode.Opcode; + +/** + * Discovers the subroutines in a method, and tracks all callers. + * + * @author Jason T. Greene + */ +public class SubroutineScanner implements Opcode { + + private Subroutine[] subroutines; + Map subTable = new HashMap(); + Set done = new HashSet(); + + + public Subroutine[] scan(MethodInfo method) throws BadBytecode { + CodeAttribute code = method.getCodeAttribute(); + CodeIterator iter = code.iterator(); + + subroutines = new Subroutine[code.getCodeLength()]; + subTable.clear(); + done.clear(); + + scan(0, iter, null); + + ExceptionTable exceptions = code.getExceptionTable(); + for (int i = 0; i < exceptions.size(); i++) { + int handler = exceptions.handlerPc(i); + // If an exception is thrown in subroutine, the handler + // is part of the same subroutine. + scan(handler, iter, subroutines[exceptions.startPc(i)]); + } + + return subroutines; + } + + private void scan(int pos, CodeIterator iter, Subroutine sub) throws BadBytecode { + // Skip already processed blocks + if (done.contains(Integer.valueOf(pos))) + return; + + done.add(Integer.valueOf(pos)); + + int old = iter.lookAhead(); + iter.move(pos); + + boolean next; + do { + pos = iter.next(); + next = scanOp(pos, iter, sub) && iter.hasNext(); + } while (next); + + iter.move(old); + } + + private boolean scanOp(int pos, CodeIterator iter, Subroutine sub) throws BadBytecode { + subroutines[pos] = sub; + + int opcode = iter.byteAt(pos); + + if (opcode == TABLESWITCH) { + scanTableSwitch(pos, iter, sub); + + return false; + } + + if (opcode == LOOKUPSWITCH) { + scanLookupSwitch(pos, iter, sub); + + return false; + } + + // All forms of return and throw end current code flow + if (Util.isReturn(opcode) || opcode == RET || opcode == ATHROW) + return false; + + if (Util.isJumpInstruction(opcode)) { + int target = Util.getJumpTarget(pos, iter); + if (opcode == JSR || opcode == JSR_W) { + Subroutine s = (Subroutine) subTable.get(Integer.valueOf(target)); + if (s == null) { + s = new Subroutine(target, pos); + subTable.put(Integer.valueOf(target), s); + scan(target, iter, s); + } else { + s.addCaller(pos); + } + } else { + scan(target, iter, sub); + + // GOTO ends current code flow + if (Util.isGoto(opcode)) + return false; + } + } + + return true; + } + + private void scanLookupSwitch(int pos, CodeIterator iter, Subroutine sub) throws BadBytecode { + int index = (pos & ~3) + 4; + // default + scan(pos + iter.s32bitAt(index), iter, sub); + int npairs = iter.s32bitAt(index += 4); + int end = npairs * 8 + (index += 4); + + // skip "match" + for (index += 4; index < end; index += 8) { + int target = iter.s32bitAt(index) + pos; + scan(target, iter, sub); + } + } + + private void scanTableSwitch(int pos, CodeIterator iter, Subroutine sub) throws BadBytecode { + // Skip 4 byte alignment padding + int index = (pos & ~3) + 4; + // default + scan(pos + iter.s32bitAt(index), iter, sub); + int low = iter.s32bitAt(index += 4); + int high = iter.s32bitAt(index += 4); + int end = (high - low + 1) * 4 + (index += 4); + + // Offset table + for (; index < end; index += 4) { + int target = iter.s32bitAt(index) + pos; + scan(target, iter, sub); + } + } + + +} diff --git a/src/main/javassist/bytecode/analysis/Type.java b/src/main/javassist/bytecode/analysis/Type.java new file mode 100644 index 00000000..38df93f7 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/Type.java @@ -0,0 +1,592 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.Map; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.NotFoundException; + +/** + * Represents a JVM type in data-flow analysis. This abstraction is necessary since + * a JVM type not only includes all normal Java types, but also a few special types + * that are used by the JVM internally. See the static field types on this class for + * more info on these special types. + * + * All primitive and special types reuse the same instance, so identity comparison can + * be used when examining them. Normal java types must use {@link #equals(Object)} to + * compare type instances. + * + * In most cases, applications which consume this API, only need to call {@link #getCtClass()} + * to obtain the needed type information. + * + * @author Jason T. Greene + */ +public class Type { + private final CtClass clazz; + private final boolean special; + + private static final Map prims = new IdentityHashMap(); + /** Represents the double primitive type */ + public static final Type DOUBLE = new Type(CtClass.doubleType); + /** Represents the boolean primitive type */ + public static final Type BOOLEAN = new Type(CtClass.booleanType); + /** Represents the long primitive type */ + public static final Type LONG = new Type(CtClass.longType); + /** Represents the char primitive type */ + public static final Type CHAR = new Type(CtClass.charType); + /** Represents the byte primitive type */ + public static final Type BYTE = new Type(CtClass.byteType); + /** Represents the short primitive type */ + public static final Type SHORT = new Type(CtClass.shortType); + /** Represents the integer primitive type */ + public static final Type INTEGER = new Type(CtClass.intType); + /** Represents the float primitive type */ + public static final Type FLOAT = new Type(CtClass.floatType); + /** Represents the void primitive type */ + public static final Type VOID = new Type(CtClass.voidType); + + /** + * Represents an unknown, or null type. This occurs when aconst_null is used. + * It is important not to treat this type as java.lang.Object, since a null can + * be assigned to any reference type. The analyzer will replace these with + * an actual known type if it can be determined by a merged path with known type + * information. If this type is encountered on a frame then it is guaranteed to + * be null, and the type information is simply not available. Any attempts to + * infer the type, without further information from the compiler would be a guess. + */ + public static final Type UNINIT = new Type(null); + + /** + * Represents an internal JVM return address, which is used by the RET + * instruction to return to a JSR that invoked the subroutine. + */ + public static final Type RETURN_ADDRESS = new Type(null, true); + + /** A placeholder used by the analyzer for the second word position of a double-word type */ + public static final Type TOP = new Type(null, true); + + /** + * Represents a non-accessible value. Code cannot access the value this type + * represents. It occurs when bytecode reuses a local variable table + * position with non-mergable types. An example would be compiled code which + * uses the same position for a primitive type in one branch, and a reference type + * in another branch. + */ + public static final Type BOGUS = new Type(null, true); + + /** Represents the java.lang.Object reference type */ + public static final Type OBJECT = lookupType("java.lang.Object"); + /** Represents the java.io.Serializable reference type */ + public static final Type SERIALIZABLE = lookupType("java.io.Serializable"); + /** Represents the java.lang.Coneable reference type */ + public static final Type CLONEABLE = lookupType("java.lang.Cloneable"); + /** Represents the java.lang.Throwable reference type */ + public static final Type THROWABLE = lookupType("java.lang.Throwable"); + + static { + prims.put(CtClass.doubleType, DOUBLE); + prims.put(CtClass.longType, LONG); + prims.put(CtClass.charType, CHAR); + prims.put(CtClass.shortType, SHORT); + prims.put(CtClass.intType, INTEGER); + prims.put(CtClass.floatType, FLOAT); + prims.put(CtClass.byteType, BYTE); + prims.put(CtClass.booleanType, BOOLEAN); + prims.put(CtClass.voidType, VOID); + + } + + /** + * Obtain the Type for a given class. If the class is a primitive, + * the the unique type instance for the primitive will be returned. + * Otherwise a new Type instance representing the class is returned. + * + * @param clazz The java class + * @return a type instance for this class + */ + public static Type get(CtClass clazz) { + Type type = (Type)prims.get(clazz); + return type != null ? type : new Type(clazz); + } + + private static Type lookupType(String name) { + try { + return new Type(ClassPool.getDefault().get(name)); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + } + + Type(CtClass clazz) { + this(clazz, false); + } + + private Type(CtClass clazz, boolean special) { + this.clazz = clazz; + this.special = special; + } + + // Used to indicate a merge internally triggered a change + boolean popChanged() { + return false; + } + + /** + * Gets the word size of this type. Double-word types, such as long and double + * will occupy two positions on the local variable table or stack. + * + * @return the number of words needed to hold this type + */ + public int getSize() { + return clazz == CtClass.doubleType || clazz == CtClass.longType || this == TOP ? 2 : 1; + } + + /** + * Returns the class this type represents. If the type is special, null will be returned. + * + * @return the class for this type, or null if special + */ + public CtClass getCtClass() { + return clazz; + } + + /** + * Returns whether or not this type is a normal java reference, i.e. it is or extends java.lang.Object. + * + * @return true if a java reference, false if a primitive or special + */ + public boolean isReference() { + return !special && (clazz == null || !clazz.isPrimitive()); + } + + /** + * Returns whether or not the type is special. A special type is one that is either used + * for internal tracking, or is only used internally by the JVM. + * + * @return true if special, false if not + */ + public boolean isSpecial() { + return special; + } + + /** + * Returns whether or not this type is an array. + * + * @return true if an array, false if not + */ + public boolean isArray() { + return clazz != null && clazz.isArray(); + } + + /** + * Returns the number of dimensions of this array. If the type is not an + * array zero is returned. + * + * @return zero if not an array, otherwise the number of array dimensions. + */ + public int getDimensions() { + if (!isArray()) return 0; + + String name = clazz.getName(); + int pos = name.length() - 2; + int count = 0; + while (name.charAt(pos) == '[' ) { + pos -= 2; + count++; + } + + return count; + } + + /** + * Returns the array component if this type is an array. If the type + * is not an array null is returned. + * + * @return the array component if an array, otherwise null + */ + public Type getComponent() { + if (this.clazz == null || !this.clazz.isArray()) + return null; + + CtClass component; + try { + component = this.clazz.getComponentType(); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + + Type type = (Type)prims.get(component); + return (type != null) ? type : new Type(component); + } + + /** + * Determines whether this type is assignable, to the passed type. + * A type is assignable to another if it is either the same type, or + * a sub-type. + * + * @param type the type to test assignability to + * @return true if this is assignable to type, otherwise false + */ + public boolean isAssignableFrom(Type type) { + if (this == type) + return true; + + if ((type == UNINIT && isReference()) || this == UNINIT && type.isReference()) + return true; + + if (type instanceof MultiType) + return ((MultiType)type).isAssignableTo(this); + + if (type instanceof MultiArrayType) + return ((MultiArrayType)type).isAssignableTo(this); + + + // Primitives and Special types must be identical + if (clazz == null || clazz.isPrimitive()) + return false; + + try { + return type.clazz.subtypeOf(clazz); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Finds the common base type, or interface which both this and the specified + * type can be assigned. If there is more than one possible answer, then a {@link MultiType}, + * or a {@link MultiArrayType} is returned. Multi-types have special rules, + * and successive merges and assignment tests on them will alter their internal state, + * as well as other multi-types they have been merged with. This method is used by + * the data-flow analyzer to merge the type state from multiple branches. + * + * @param type the type to merge with + * @return the merged type + */ + public Type merge(Type type) { + if (type == this) + return this; + if (type == null) + return this; + if (type == Type.UNINIT) + return this; + if (this == Type.UNINIT) + return type; + + // Unequal primitives and special types can not be merged + if (! type.isReference() || ! this.isReference()) + return BOGUS; + + // Centralize merging of multi-interface types + if (type instanceof MultiType) + return type.merge(this); + + if (type.isArray() && this.isArray()) + return mergeArray(type); + + try { + return mergeClasses(type); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + } + + Type getRootComponent(Type type) { + while (type.isArray()) + type = type.getComponent(); + + return type; + } + + private Type createArray(Type rootComponent, int dims) { + if (rootComponent instanceof MultiType) + return new MultiArrayType((MultiType) rootComponent, dims); + + String name = arrayName(rootComponent.clazz.getName(), dims); + + Type type; + try { + type = Type.get(getClassPool(rootComponent).get(name)); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + + return type; + } + + String arrayName(String component, int dims) { + // Using char[] since we have no StringBuilder in JDK4, and StringBuffer is slow. + // Although, this is more efficient even if we did have one. + int i = component.length(); + int size = i + dims * 2; + char[] string = new char[size]; + component.getChars(0, i, string, 0); + while (i < size) { + string[i++] = '['; + string[i++] = ']'; + } + component = new String(string); + return component; + } + + private ClassPool getClassPool(Type rootComponent) { + ClassPool pool = rootComponent.clazz.getClassPool(); + return pool != null ? pool : ClassPool.getDefault(); + } + + private Type mergeArray(Type type) { + Type typeRoot = getRootComponent(type); + Type thisRoot = getRootComponent(this); + int typeDims = type.getDimensions(); + int thisDims = this.getDimensions(); + + // Array commponents can be merged when the dimensions are equal + if (typeDims == thisDims) { + Type mergedComponent = thisRoot.merge(typeRoot); + + // If the components can not be merged (a primitive component mixed with a different type) + // then Object is the common type. + if (mergedComponent == Type.BOGUS) + return Type.OBJECT; + + return createArray(mergedComponent, thisDims); + } + + Type targetRoot; + int targetDims; + + if (typeDims < thisDims) { + targetRoot = typeRoot; + targetDims = typeDims; + } else { + targetRoot = thisRoot; + targetDims = thisDims; + } + + // Special case, arrays are cloneable and serializable, so prefer them when dimensions differ + if (eq(CLONEABLE.clazz, targetRoot.clazz) || eq(SERIALIZABLE.clazz, targetRoot.clazz)) + return createArray(targetRoot, targetDims); + + return createArray(OBJECT, targetDims); + } + + private static CtClass findCommonSuperClass(CtClass one, CtClass two) throws NotFoundException { + CtClass deep = one; + CtClass shallow = two; + CtClass backupShallow = shallow; + CtClass backupDeep = deep; + + // Phase 1 - Find the deepest hierarchy, set deep and shallow correctly + for (;;) { + // In case we get lucky, and find a match early + if (eq(deep, shallow) && deep.getSuperclass() != null) + return deep; + + CtClass deepSuper = deep.getSuperclass(); + CtClass shallowSuper = shallow.getSuperclass(); + + if (shallowSuper == null) { + // right, now reset shallow + shallow = backupShallow; + break; + } + + if (deepSuper == null) { + // wrong, swap them, since deep is now useless, its our tmp before we swap it + deep = backupDeep; + backupDeep = backupShallow; + backupShallow = deep; + + deep = shallow; + shallow = backupShallow; + break; + } + + deep = deepSuper; + shallow = shallowSuper; + } + + // Phase 2 - Move deepBackup up by (deep end - deep) + for (;;) { + deep = deep.getSuperclass(); + if (deep == null) + break; + + backupDeep = backupDeep.getSuperclass(); + } + + deep = backupDeep; + + // Phase 3 - The hierarchy positions are now aligned + // The common super class is easy to find now + while (!eq(deep, shallow)) { + deep = deep.getSuperclass(); + shallow = shallow.getSuperclass(); + } + + return deep; + } + + private Type mergeClasses(Type type) throws NotFoundException { + CtClass superClass = findCommonSuperClass(this.clazz, type.clazz); + + // If its Object, then try and find a common interface(s) + if (superClass.getSuperclass() == null) { + Map interfaces = findCommonInterfaces(type); + if (interfaces.size() == 1) + return new Type((CtClass) interfaces.values().iterator().next()); + if (interfaces.size() > 1) + return new MultiType(interfaces); + + // Only Object is in common + return new Type(superClass); + } + + // Check for a common interface that is not on the found supertype + Map commonDeclared = findExclusiveDeclaredInterfaces(type, superClass); + if (commonDeclared.size() > 0) { + return new MultiType(commonDeclared, new Type(superClass)); + } + + return new Type(superClass); + } + + private Map findCommonInterfaces(Type type) { + Map typeMap = getAllInterfaces(type.clazz, null); + Map thisMap = getAllInterfaces(this.clazz, null); + + return findCommonInterfaces(typeMap, thisMap); + } + + private Map findExclusiveDeclaredInterfaces(Type type, CtClass exclude) { + Map typeMap = getDeclaredInterfaces(type.clazz, null); + Map thisMap = getDeclaredInterfaces(this.clazz, null); + Map excludeMap = getAllInterfaces(exclude, null); + + Iterator i = excludeMap.keySet().iterator(); + while (i.hasNext()) { + Object intf = i.next(); + typeMap.remove(intf); + thisMap.remove(intf); + } + + return findCommonInterfaces(typeMap, thisMap); + } + + + Map findCommonInterfaces(Map typeMap, Map alterMap) { + Iterator i = alterMap.keySet().iterator(); + while (i.hasNext()) { + if (! typeMap.containsKey(i.next())) + i.remove(); + } + + // Reduce to subinterfaces + // This does not need to be recursive since we make a copy, + // and that copy contains all super types for the whole hierarchy + i = new ArrayList(alterMap.values()).iterator(); + while (i.hasNext()) { + CtClass intf = (CtClass) i.next(); + CtClass[] interfaces; + try { + interfaces = intf.getInterfaces(); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + + for (int c = 0; c < interfaces.length; c++) + alterMap.remove(interfaces[c].getName()); + } + + return alterMap; + } + + Map getAllInterfaces(CtClass clazz, Map map) { + if (map == null) + map = new HashMap(); + + if (clazz.isInterface()) + map.put(clazz.getName(), clazz); + do { + try { + CtClass[] interfaces = clazz.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + CtClass intf = interfaces[i]; + map.put(intf.getName(), intf); + getAllInterfaces(intf, map); + } + + clazz = clazz.getSuperclass(); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + } while (clazz != null); + + return map; + } + + Map getDeclaredInterfaces(CtClass clazz, Map map) { + if (map == null) + map = new HashMap(); + + if (clazz.isInterface()) + map.put(clazz.getName(), clazz); + + CtClass[] interfaces; + try { + interfaces = clazz.getInterfaces(); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + + for (int i = 0; i < interfaces.length; i++) { + CtClass intf = interfaces[i]; + map.put(intf.getName(), intf); + getDeclaredInterfaces(intf, map); + } + + return map; + } + + public boolean equals(Object o) { + if (! (o instanceof Type)) + return false; + + return o.getClass() == getClass() && eq(clazz, ((Type)o).clazz); + } + + static boolean eq(CtClass one, CtClass two) { + return one == two || (one != null && two != null && one.getName().equals(two.getName())); + } + + public String toString() { + if (this == BOGUS) + return "BOGUS"; + if (this == UNINIT) + return "UNINIT"; + if (this == RETURN_ADDRESS) + return "RETURN ADDRESS"; + if (this == TOP) + return "TOP"; + + return clazz == null ? "null" : clazz.getName(); + } +} diff --git a/src/main/javassist/bytecode/analysis/Util.java b/src/main/javassist/bytecode/analysis/Util.java new file mode 100644 index 00000000..a8cdfcca --- /dev/null +++ b/src/main/javassist/bytecode/analysis/Util.java @@ -0,0 +1,47 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + +import javassist.bytecode.CodeIterator; +import javassist.bytecode.Opcode; + +/** + * A set of common utility methods. + * + * @author Jason T. Greene + */ +public class Util implements Opcode { + public static int getJumpTarget(int pos, CodeIterator iter) { + int opcode = iter.byteAt(pos); + pos += (opcode == JSR_W || opcode == GOTO_W) ? iter.s32bitAt(pos + 1) : iter.s16bitAt(pos + 1); + return pos; + } + + public static boolean isJumpInstruction(int opcode) { + return (opcode >= IFEQ && opcode <= JSR) || opcode == IFNULL || opcode == IFNONNULL || opcode == JSR_W || opcode == GOTO_W; + } + + public static boolean isGoto(int opcode) { + return opcode == GOTO || opcode == GOTO_W; + } + + public static boolean isJsr(int opcode) { + return opcode == JSR || opcode == JSR_W; + } + + public static boolean isReturn(int opcode) { + return (opcode >= IRETURN && opcode <= RETURN); + } +} diff --git a/src/main/javassist/bytecode/analysis/package.html b/src/main/javassist/bytecode/analysis/package.html new file mode 100644 index 00000000..b141670b --- /dev/null +++ b/src/main/javassist/bytecode/analysis/package.html @@ -0,0 +1,19 @@ +<html> +<body> +Bytecode Analysis API. + +<p>This package provides an API for performing data-flow analysis on a method's bytecode. +This allows the user to determine the type state of the stack and local variable table +at the start of every instruction. In addition this API can be used to validate +bytecode, find dead bytecode, and identify unnecessary checkcasts. + +<p>The users of this package must know the specifications of +class file and Java bytecode. For more details, read this book: + +<ul>Tim Lindholm and Frank Yellin, +"The Java Virtual Machine Specification 2nd Ed.", +Addison-Wesley, 1999. +</ul> + +</body> +</html> diff --git a/src/main/javassist/convert/TransformAccessArrayField.java b/src/main/javassist/convert/TransformAccessArrayField.java index d3036056..1af424ed 100644 --- a/src/main/javassist/convert/TransformAccessArrayField.java +++ b/src/main/javassist/convert/TransformAccessArrayField.java @@ -14,168 +14,251 @@ */ package javassist.convert; +import javassist.CannotCompileException; import javassist.CtClass; import javassist.NotFoundException; import javassist.CodeConverter.ArrayAccessReplacementMethodNames; import javassist.bytecode.BadBytecode; import javassist.bytecode.CodeIterator; import javassist.bytecode.ConstPool; +import javassist.bytecode.MethodInfo; +import javassist.bytecode.analysis.Analyzer; +import javassist.bytecode.analysis.Frame; /** - * + * A transformer which replaces array access with static method invocations. + * * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> - * @version $Revision: 1.4 $ + * @author Jason T. Greene + * @version $Revision: 1.5 $ */ -public class TransformAccessArrayField extends Transformer { -// CtClass componentType; - - String methodClassname; - ArrayAccessReplacementMethodNames names; - - public TransformAccessArrayField(Transformer next, String methodClassname, - ArrayAccessReplacementMethodNames names) - throws NotFoundException - { - super(next); - this.methodClassname = methodClassname; - this.names = names; - } - - public int transform(CtClass tclazz, int pos, CodeIterator iterator, - ConstPool cp) throws BadBytecode - { - int c = iterator.byteAt(pos); - - if (c == AALOAD || c == BALOAD || c == CALOAD || c == DALOAD - || c == FALOAD || c == IALOAD || c == LALOAD || c == SALOAD) - replace(cp, iterator, pos, c, getLoadReplacementSignature(c)); - else if (c == AASTORE || c == BASTORE || c == CASTORE || c == DASTORE - || c == FASTORE || c == IASTORE || c == LASTORE || c == SASTORE) - replace(cp, iterator, pos, c, getStoreReplacementSignature(c)); - - return pos; - } - - private void replace(ConstPool cp, CodeIterator iterator, - int pos, int opcode, String signature) - throws BadBytecode - { - String methodName = getMethodName(opcode); - if (methodName != null) { - iterator.insertGap(2); - int mi = cp.addClassInfo(methodClassname); - int methodref = cp.addMethodrefInfo(mi, methodName, signature); - iterator.writeByte(INVOKESTATIC, pos); - iterator.write16bit(methodref, pos + 1); - } - } - - private String getMethodName(int opcode) { - String methodName = null; - switch (opcode) { - case AALOAD: - methodName = names.objectRead(); - break; - case BALOAD: - methodName = names.byteOrBooleanRead(); - break; - case CALOAD: - methodName = names.charRead(); - break; - case DALOAD: - methodName = names.doubleRead(); - break; - case FALOAD: - methodName = names.floatRead(); - break; - case IALOAD: - methodName = names.intRead(); - break; - case SALOAD: - methodName = names.shortRead(); - break; - case LALOAD: - methodName = names.longRead(); - break; - case AASTORE: - methodName = names.objectWrite(); - break; - case BASTORE: - methodName = names.byteOrBooleanWrite(); - break; - case CASTORE: - methodName = names.charWrite(); - break; - case DASTORE: - methodName = names.doubleWrite(); - break; - case FASTORE: - methodName = names.floatWrite(); - break; - case IASTORE: - methodName = names.intWrite(); - break; - case SASTORE: - methodName = names.shortWrite(); - break; - case LASTORE: - methodName = names.longWrite(); - break; - } +public final class TransformAccessArrayField extends Transformer { + private final String methodClassname; + private final ArrayAccessReplacementMethodNames names; + private Frame[] frames; + private int offset; - if (methodName.equals("")) - methodName = null; + public TransformAccessArrayField(Transformer next, String methodClassname, + ArrayAccessReplacementMethodNames names) throws NotFoundException { + super(next); + this.methodClassname = methodClassname; + this.names = names; - return methodName; - } + } - private String getLoadReplacementSignature(int opcode) - throws BadBytecode - { - switch (opcode) { - case AALOAD: - return "(Ljava/lang/Object;I)Ljava/lang/Object;"; - case BALOAD: - return "(Ljava/lang/Object;I)B"; - case CALOAD: - return "(Ljava/lang/Object;I)C"; - case DALOAD: - return "(Ljava/lang/Object;I)D"; - case FALOAD: - return "(Ljava/lang/Object;I)F"; - case IALOAD: - return "(Ljava/lang/Object;I)I"; - case SALOAD: - return "(Ljava/lang/Object;I)S"; - case LALOAD: - return "(Ljava/lang/Object;I)J"; - } + public void initialize(ConstPool cp, CtClass clazz, MethodInfo minfo) throws CannotCompileException { + /* + * This transformer must be isolated from other transformers, since some + * of them affect the local variable and stack maximums without updating + * the code attribute to reflect the changes. This screws up the + * data-flow analyzer, since it relies on consistent code state. Even + * if the attribute values were updated correctly, we would have to + * detect it, and redo analysis, which is not cheap. Instead, we are + * better off doing all changes in initialize() before everyone else has + * a chance to muck things up. + */ + CodeIterator iterator = minfo.getCodeAttribute().iterator(); + while (iterator.hasNext()) { + try { + int pos = iterator.next(); + int c = iterator.byteAt(pos); - throw new BadBytecode(opcode); - } - - private String getStoreReplacementSignature(int opcode) - throws BadBytecode - { - switch (opcode) { - case AASTORE: - return "(Ljava/lang/Object;ILjava/lang/Object;)V"; - case BASTORE: - return "(Ljava/lang/Object;IB)V"; - case CASTORE: - return "(Ljava/lang/Object;IC)V"; - case DASTORE: - return "(Ljava/lang/Object;ID)V"; - case FASTORE: - return "(Ljava/lang/Object;IF)V"; - case IASTORE: - return "(Ljava/lang/Object;II)V"; - case SASTORE: - return "(Ljava/lang/Object;IS)V"; - case LASTORE: - return "(Ljava/lang/Object;IJ)V"; - } + if (c == AALOAD) + initFrames(clazz, minfo); + + if (c == AALOAD || c == BALOAD || c == CALOAD || c == DALOAD + || c == FALOAD || c == IALOAD || c == LALOAD + || c == SALOAD) { + pos = replace(cp, iterator, pos, c, getLoadReplacementSignature(c)); + } else if (c == AASTORE || c == BASTORE || c == CASTORE + || c == DASTORE || c == FASTORE || c == IASTORE + || c == LASTORE || c == SASTORE) { + pos = replace(cp, iterator, pos, c, getStoreReplacementSignature(c)); + } - throw new BadBytecode(opcode); + } catch (Exception e) { + throw new CannotCompileException(e); + } + } } + + public void clean() { + frames = null; + offset = -1; + } + + public int transform(CtClass tclazz, int pos, CodeIterator iterator, + ConstPool cp) throws BadBytecode { + // Do nothing, see above comment + return pos; + } + + private Frame getFrame(int pos) throws BadBytecode { + return frames[pos - offset]; // Adjust pos + } + + private void initFrames(CtClass clazz, MethodInfo minfo) throws BadBytecode { + if (frames == null) { + frames = ((new Analyzer())).analyze(clazz, minfo); + offset = 0; // start tracking changes + } + } + + private int updatePos(int pos, int increment) { + if (offset > -1) + offset += increment; + + return pos + increment; + } + + private String getTopType(int pos) throws BadBytecode { + Frame frame = getFrame(pos); + if (frame == null) + return null; + + CtClass clazz = frame.peek().getCtClass(); + return clazz != null ? clazz.getName() : null; + } + + private int replace(ConstPool cp, CodeIterator iterator, int pos, + int opcode, String signature) throws BadBytecode { + String castType = null; + String methodName = getMethodName(opcode); + if (methodName != null) { + // See if the object must be cast + if (opcode == AALOAD) { + castType = getTopType(iterator.lookAhead()); + // Do not replace an AALOAD instruction that we do not have a type for + // This happens when the state is guaranteed to be null (Type.UNINIT) + // So we don't really care about this case. + if (castType == null) + return pos; + if ("java.lang.Object".equals(castType)) + castType = null; + } + + // The gap may include extra padding + int gapLength = iterator.insertGap(pos, castType != null ? 5 : 2); + + int mi = cp.addClassInfo(methodClassname); + int methodref = cp.addMethodrefInfo(mi, methodName, signature); + iterator.writeByte(INVOKESTATIC, pos); + iterator.write16bit(methodref, pos + 1); + + if (castType != null) { + int index = cp.addClassInfo(castType); + iterator.writeByte(CHECKCAST, pos + 3); + iterator.write16bit(index, pos + 4); + } + + pos = updatePos(pos, gapLength); + } + + return pos; + } + + private String getMethodName(int opcode) { + String methodName = null; + switch (opcode) { + case AALOAD: + methodName = names.objectRead(); + break; + case BALOAD: + methodName = names.byteOrBooleanRead(); + break; + case CALOAD: + methodName = names.charRead(); + break; + case DALOAD: + methodName = names.doubleRead(); + break; + case FALOAD: + methodName = names.floatRead(); + break; + case IALOAD: + methodName = names.intRead(); + break; + case SALOAD: + methodName = names.shortRead(); + break; + case LALOAD: + methodName = names.longRead(); + break; + case AASTORE: + methodName = names.objectWrite(); + break; + case BASTORE: + methodName = names.byteOrBooleanWrite(); + break; + case CASTORE: + methodName = names.charWrite(); + break; + case DASTORE: + methodName = names.doubleWrite(); + break; + case FASTORE: + methodName = names.floatWrite(); + break; + case IASTORE: + methodName = names.intWrite(); + break; + case SASTORE: + methodName = names.shortWrite(); + break; + case LASTORE: + methodName = names.longWrite(); + break; + } + + if (methodName.equals("")) + methodName = null; + + return methodName; + } + + private String getLoadReplacementSignature(int opcode) throws BadBytecode { + switch (opcode) { + case AALOAD: + return "(Ljava/lang/Object;I)Ljava/lang/Object;"; + case BALOAD: + return "(Ljava/lang/Object;I)B"; + case CALOAD: + return "(Ljava/lang/Object;I)C"; + case DALOAD: + return "(Ljava/lang/Object;I)D"; + case FALOAD: + return "(Ljava/lang/Object;I)F"; + case IALOAD: + return "(Ljava/lang/Object;I)I"; + case SALOAD: + return "(Ljava/lang/Object;I)S"; + case LALOAD: + return "(Ljava/lang/Object;I)J"; + } + + throw new BadBytecode(opcode); + } + + private String getStoreReplacementSignature(int opcode) throws BadBytecode { + switch (opcode) { + case AASTORE: + return "(Ljava/lang/Object;ILjava/lang/Object;)V"; + case BASTORE: + return "(Ljava/lang/Object;IB)V"; + case CASTORE: + return "(Ljava/lang/Object;IC)V"; + case DASTORE: + return "(Ljava/lang/Object;ID)V"; + case FASTORE: + return "(Ljava/lang/Object;IF)V"; + case IASTORE: + return "(Ljava/lang/Object;II)V"; + case SASTORE: + return "(Ljava/lang/Object;IS)V"; + case LASTORE: + return "(Ljava/lang/Object;IJ)V"; + } + + throw new BadBytecode(opcode); + } } diff --git a/src/main/javassist/convert/Transformer.java b/src/main/javassist/convert/Transformer.java index d4ea0ecf..1095cf5b 100644 --- a/src/main/javassist/convert/Transformer.java +++ b/src/main/javassist/convert/Transformer.java @@ -15,9 +15,14 @@ package javassist.convert; -import javassist.bytecode.*; -import javassist.CtClass; import javassist.CannotCompileException; +import javassist.CtClass; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.CodeAttribute; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.ConstPool; +import javassist.bytecode.MethodInfo; +import javassist.bytecode.Opcode; /** * Transformer and its subclasses are used for executing @@ -35,6 +40,10 @@ public abstract class Transformer implements Opcode { public Transformer getNext() { return next; } public void initialize(ConstPool cp, CodeAttribute attr) {} + + public void initialize(ConstPool cp, CtClass clazz, MethodInfo minfo) throws CannotCompileException { + initialize(cp, minfo.getCodeAttribute()); + } public void clean() {} diff --git a/src/main/javassist/tools/framedump.java b/src/main/javassist/tools/framedump.java new file mode 100644 index 00000000..0a30fb1d --- /dev/null +++ b/src/main/javassist/tools/framedump.java @@ -0,0 +1,47 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.tools; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.bytecode.analysis.FramePrinter; + +/** + * framedump is a tool for viewing a merged combination of the instructions and frame state + * of all methods in a class. + * + * <p>For example, + * <ul><pre>% java javassist.tools.framedump foo.class</pre></ul> + */ +public class framedump { + private framedump() {} + + /** + * Main method. + * + * @param args <code>args[0]</code> is the class file name. + */ + public static void main(String[] args) throws Exception { + if (args.length != 1) { + System.err.println("Usage: java javassist.tools.framedump <class file name>"); + return; + } + + ClassPool pool = ClassPool.getDefault(); + CtClass clazz = pool.get(args[0]); + System.out.println("Frame Dump of " + clazz.getName() + ":"); + FramePrinter.print(clazz, System.out); + } +} |