diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/main/javassist/CodeConverter.java | 8 | ||||
-rw-r--r-- | src/main/javassist/CtNewMethod.java | 9 | ||||
-rw-r--r-- | src/main/javassist/CtNewWrappedConstructor.java | 1 | ||||
-rw-r--r-- | src/main/javassist/CtNewWrappedMethod.java | 4 | ||||
-rw-r--r-- | src/main/javassist/bytecode/ConstPool.java | 2 | ||||
-rw-r--r-- | src/main/javassist/bytecode/StackMapTable.java | 10 | ||||
-rw-r--r-- | src/main/javassist/bytecode/stackmap/BasicBlock.java | 2 | ||||
-rw-r--r-- | src/main/javassist/bytecode/stackmap/Liveness.java | 366 | ||||
-rw-r--r-- | src/main/javassist/bytecode/stackmap/MapMaker.java | 227 | ||||
-rw-r--r-- | src/main/javassist/bytecode/stackmap/Tracer.java | 103 | ||||
-rw-r--r-- | src/main/javassist/bytecode/stackmap/TypeData.java | 846 | ||||
-rw-r--r-- | src/main/javassist/bytecode/stackmap/TypeTag.java | 3 | ||||
-rw-r--r-- | src/main/javassist/bytecode/stackmap/TypedBlock.java | 32 | ||||
-rw-r--r-- | src/test/javassist/JvstTest.java | 2 | ||||
-rw-r--r-- | src/test/javassist/JvstTest4.java | 3 | ||||
-rw-r--r-- | src/test/javassist/bytecode/StackMapTest.java | 315 | ||||
-rw-r--r-- | src/test/javassist/tools/reflect/LoaderTest.java | 4 |
17 files changed, 1016 insertions, 921 deletions
diff --git a/src/main/javassist/CodeConverter.java b/src/main/javassist/CodeConverter.java index 8873e560..399454ec 100644 --- a/src/main/javassist/CodeConverter.java +++ b/src/main/javassist/CodeConverter.java @@ -532,6 +532,14 @@ public class CodeConverter { if (stack > 0) codeAttr.setMaxStack(codeAttr.getMaxStack() + stack); + + try { + minfo.rebuildStackMapIf6(clazz.getClassPool(), + clazz.getClassFile2()); + } + catch (BadBytecode b) { + throw new CannotCompileException(b.getMessage(), b); + } } /** diff --git a/src/main/javassist/CtNewMethod.java b/src/main/javassist/CtNewMethod.java index bddf081a..f134dcdd 100644 --- a/src/main/javassist/CtNewMethod.java +++ b/src/main/javassist/CtNewMethod.java @@ -246,7 +246,9 @@ public class CtNewMethod { } minfo.setCodeAttribute(code.toCodeAttribute()); - return new CtMethod(minfo, field.getDeclaringClass()); + CtClass cc = field.getDeclaringClass(); + // a stack map is not needed. + return new CtMethod(minfo, cc); } /** @@ -290,7 +292,9 @@ public class CtNewMethod { } minfo.setCodeAttribute(code.toCodeAttribute()); - return new CtMethod(minfo, field.getDeclaringClass()); + CtClass cc = field.getDeclaringClass(); + // a stack map is not needed. + return new CtMethod(minfo, cc); } /** @@ -359,6 +363,7 @@ public class CtNewMethod { code.setMaxLocals(++s); code.setMaxStack(s < 2 ? 2 : s); // for a 2-word return value minfo.setCodeAttribute(code.toCodeAttribute()); + // a stack map is not needed. return new CtMethod(minfo, declaring); } diff --git a/src/main/javassist/CtNewWrappedConstructor.java b/src/main/javassist/CtNewWrappedConstructor.java index 091a6c9a..7163f1ce 100644 --- a/src/main/javassist/CtNewWrappedConstructor.java +++ b/src/main/javassist/CtNewWrappedConstructor.java @@ -39,6 +39,7 @@ class CtNewWrappedConstructor extends CtNewWrappedMethod { howToCallSuper, body, parameterTypes, constParam); cons.getMethodInfo2().setCodeAttribute(code.toCodeAttribute()); + // a stack map table is not needed. return cons; } catch (NotFoundException e) { diff --git a/src/main/javassist/CtNewWrappedMethod.java b/src/main/javassist/CtNewWrappedMethod.java index f3030f7d..66504bed 100644 --- a/src/main/javassist/CtNewWrappedMethod.java +++ b/src/main/javassist/CtNewWrappedMethod.java @@ -44,7 +44,9 @@ class CtNewWrappedMethod { Bytecode code = makeBody(declaring, declaring.getClassFile2(), body, parameterTypes, returnType, constParam); - mt.getMethodInfo2().setCodeAttribute(code.toCodeAttribute()); + MethodInfo minfo = mt.getMethodInfo2(); + minfo.setCodeAttribute(code.toCodeAttribute()); + // a stack map has been already created. return mt; } diff --git a/src/main/javassist/bytecode/ConstPool.java b/src/main/javassist/bytecode/ConstPool.java index 3112e440..27124ac1 100644 --- a/src/main/javassist/bytecode/ConstPool.java +++ b/src/main/javassist/bytecode/ConstPool.java @@ -174,7 +174,7 @@ public final class ConstPool { * @return a fully-qualified class or interface name specified * by <code>name_index</code>. If the type is an array * type, this method returns an encoded name like - * <code>[java.lang.Object;</code> (note that the separators + * <code>[Ljava.lang.Object;</code> (note that the separators * are not slashes but dots). * @see javassist.ClassPool#getCtClass(String) */ diff --git a/src/main/javassist/bytecode/StackMapTable.java b/src/main/javassist/bytecode/StackMapTable.java index 333d038e..15726be2 100644 --- a/src/main/javassist/bytecode/StackMapTable.java +++ b/src/main/javassist/bytecode/StackMapTable.java @@ -67,7 +67,7 @@ public class StackMapTable extends AttributeInfo { { try { return new StackMapTable(newCp, - new Copier(this.constPool, info, newCp).doit()); + new Copier(this.constPool, info, newCp, classnames).doit()); } catch (BadBytecode e) { throw new RuntimeCopyException("bad bytecode. fatal?"); @@ -400,16 +400,18 @@ public class StackMapTable extends AttributeInfo { static class Copier extends SimpleCopy { private ConstPool srcPool, destPool; + private Map classnames; - public Copier(ConstPool src, byte[] data, ConstPool dest) { + public Copier(ConstPool src, byte[] data, ConstPool dest, Map names) { super(data); srcPool = src; destPool = dest; + classnames = names; } protected int copyData(int tag, int data) { if (tag == OBJECT) - return srcPool.copy(data, destPool, null); + return srcPool.copy(data, destPool, classnames); else return data; } @@ -418,7 +420,7 @@ public class StackMapTable extends AttributeInfo { int[] newData = new int[data.length]; for (int i = 0; i < data.length; i++) if (tags[i] == OBJECT) - newData[i] = srcPool.copy(data[i], destPool, null); + newData[i] = srcPool.copy(data[i], destPool, classnames); else newData[i] = data[i]; diff --git a/src/main/javassist/bytecode/stackmap/BasicBlock.java b/src/main/javassist/bytecode/stackmap/BasicBlock.java index 073099cf..9afe1a3a 100644 --- a/src/main/javassist/bytecode/stackmap/BasicBlock.java +++ b/src/main/javassist/bytecode/stackmap/BasicBlock.java @@ -274,7 +274,7 @@ public class BasicBlock { else if (op == Opcode.JSR_W) makeJsr(marks, index, index + ci.s32bitAt(index + 1), 5); else if (op == Opcode.WIDE && ci.byteAt(index + 1) == Opcode.RET) - makeMark(marks, index, null, 1, true); + makeMark(marks, index, null, 4, true); } if (et != null) { diff --git a/src/main/javassist/bytecode/stackmap/Liveness.java b/src/main/javassist/bytecode/stackmap/Liveness.java deleted file mode 100644 index c600c6c2..00000000 --- a/src/main/javassist/bytecode/stackmap/Liveness.java +++ /dev/null @@ -1,366 +0,0 @@ -/* - * Javassist, a Java-bytecode translator toolkit. - * Copyright (C) 1999- Shigeru Chiba. 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, - * or the Apache License Version 2.0. - * - * 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.stackmap; - -import javassist.bytecode.*; - -public class Liveness { - protected static final byte UNKNOWN = 0; - protected static final byte READ = 1; - protected static final byte UPDATED = 2; - protected byte[] localsUsage; - - /** - * If true, all the arguments become alive within the whole method body. - * - * To correctly compute a stack map table, all the arguments must - * be alive (localsUsage[?] must be READ) at least in the first block. - */ - public static boolean useArgs = true; - - public void compute(CodeIterator ci, TypedBlock[] blocks, int maxLocals, - TypeData[] args) - throws BadBytecode - { - computeUsage(ci, blocks, maxLocals); - if (useArgs) - useAllArgs(blocks, args); - - computeLiveness1(blocks[0]); - while (hasChanged(blocks)) - computeLiveness2(blocks[0]); - } - - private void useAllArgs(TypedBlock[] blocks, TypeData[] args) { - for (int k = 0; k < blocks.length; k++) { - byte[] usage = blocks[k].localsUsage; - for (int i = 0; i < args.length; i++) - if (args[i] != TypeTag.TOP) - usage[i] = READ; - } - } - - static final int NOT_YET = 0; - static final int CHANGED_LAST = 1; - static final int DONE = 2; - static final int CHANGED_NOW = 3; - - private void computeLiveness1(TypedBlock tb) { - if (tb.updating) { - // a loop was detected. - computeLiveness1u(tb); - return; - } - - if (tb.inputs != null) - return; - - tb.updating = true; - byte[] usage = tb.localsUsage; - int n = usage.length; - boolean[] in = new boolean[n]; - for (int i = 0; i < n; i++) - in[i] = usage[i] == READ; - - BasicBlock.Catch handlers = tb.toCatch; - while (handlers != null) { - TypedBlock h = (TypedBlock)handlers.body; - computeLiveness1(h); - for (int k = 0; k < n; k++) - if (h.inputs[k]) - in[k] = true; - - handlers = handlers.next; - } - - if (tb.exit != null) { - for (int i = 0; i < tb.exit.length; i++) { - TypedBlock e = (TypedBlock)tb.exit[i]; - computeLiveness1(e); - for (int k = 0; k < n; k++) - if (!in[k]) - in[k] = usage[k] == UNKNOWN && e.inputs[k]; - } - } - - tb.updating = false; - if (tb.inputs == null) { - tb.inputs = in; - tb.status = DONE; - } - else { - for (int i = 0; i < n; i++) - if (in[i] && !tb.inputs[i]) { - tb.inputs[i] = true; - tb.status = CHANGED_NOW; - } - } - } - - private void computeLiveness1u(TypedBlock tb) { - if (tb.inputs == null) { - byte[] usage = tb.localsUsage; - int n = usage.length; - boolean[] in = new boolean[n]; - for (int i = 0; i < n; i++) - in[i] = usage[i] == READ; - - tb.inputs = in; - tb.status = DONE; - } - } - - private void computeLiveness2(TypedBlock tb) { - if (tb.updating || tb.status >= DONE) - return; - - tb.updating = true; - if (tb.exit == null) - tb.status = DONE; - else { - boolean changed = false; - for (int i = 0; i < tb.exit.length; i++) { - TypedBlock e = (TypedBlock)tb.exit[i]; - computeLiveness2(e); - if (e.status != DONE) - changed = true; - } - - if (changed) { - changed = false; - byte[] usage = tb.localsUsage; - int n = usage.length; - for (int i = 0; i < tb.exit.length; i++) { - TypedBlock e = (TypedBlock)tb.exit[i]; - if (e.status != DONE) - for (int k = 0; k < n; k++) - if (!tb.inputs[k]) { - if (usage[k] == UNKNOWN && e.inputs[k]) { - tb.inputs[k] = true; - changed = true; - } - } - } - - tb.status = changed ? CHANGED_NOW : DONE; - } - else - tb.status = DONE; - } - - if (computeLiveness2except(tb)) - tb.status = CHANGED_NOW; - - tb.updating = false; - } - - private boolean computeLiveness2except(TypedBlock tb) { - BasicBlock.Catch handlers = tb.toCatch; - boolean changed = false; - while (handlers != null) { - TypedBlock h = (TypedBlock)handlers.body; - computeLiveness2(h); - if (h.status != DONE) { - boolean[] in = tb.inputs; - int n = in.length; - for (int k = 0; k < n; k++) - if (!in[k] && h.inputs[k]) { - in[k] = true; - changed = true; - } - } - - handlers = handlers.next; - } - - return changed; - } - - private boolean hasChanged(TypedBlock[] blocks) { - int n = blocks.length; - boolean changed = false; - for (int i = 0; i < n; i++) { - TypedBlock tb = blocks[i]; - if (tb.status == CHANGED_NOW) { - tb.status = CHANGED_LAST; - changed = true; - } - else - tb.status = NOT_YET; - } - - return changed; - } - - private void computeUsage(CodeIterator ci, TypedBlock[] blocks, int maxLocals) - throws BadBytecode - { - int n = blocks.length; - for (int i = 0; i < n; i++) { - TypedBlock tb = blocks[i]; - localsUsage = tb.localsUsage = new byte[maxLocals]; - int pos = tb.position; - analyze(ci, pos, pos + tb.length); - localsUsage = null; - } - } - - protected final void readLocal(int reg) { - if (localsUsage[reg] == UNKNOWN) - localsUsage[reg] = READ; - } - - protected final void writeLocal(int reg) { - if (localsUsage[reg] == UNKNOWN) - localsUsage[reg] = UPDATED; - } - - protected void analyze(CodeIterator ci, int begin, int end) - throws BadBytecode - { - ci.begin(); - ci.move(begin); - while (ci.hasNext()) { - int index = ci.next(); - if (index >= end) - break; - - int op = ci.byteAt(index); - if (op < 96) - if (op < 54) - doOpcode0_53(ci, index, op); - else - doOpcode54_95(ci, index, op); - else - if (op == Opcode.IINC) { - // this does not call writeLocal(). - readLocal(ci.byteAt(index + 1)); - } - else if (op == Opcode.WIDE) - doWIDE(ci, index); - } - } - - private void doOpcode0_53(CodeIterator ci, int pos, int op) { - switch (op) { - case Opcode.ILOAD : - case Opcode.LLOAD : - case Opcode.FLOAD : - case Opcode.DLOAD : - case Opcode.ALOAD : - readLocal(ci.byteAt(pos + 1)); - break; - case Opcode.ILOAD_0 : - case Opcode.ILOAD_1 : - case Opcode.ILOAD_2 : - case Opcode.ILOAD_3 : - readLocal(op - Opcode.ILOAD_0); - break; - case Opcode.LLOAD_0 : - case Opcode.LLOAD_1 : - case Opcode.LLOAD_2 : - case Opcode.LLOAD_3 : - readLocal(op - Opcode.LLOAD_0); - break; - case Opcode.FLOAD_0 : - case Opcode.FLOAD_1 : - case Opcode.FLOAD_2 : - case Opcode.FLOAD_3 : - readLocal(op - Opcode.FLOAD_0); - break; - case Opcode.DLOAD_0 : - case Opcode.DLOAD_1 : - case Opcode.DLOAD_2 : - case Opcode.DLOAD_3 : - readLocal(op - Opcode.DLOAD_0); - break; - case Opcode.ALOAD_0 : - case Opcode.ALOAD_1 : - case Opcode.ALOAD_2 : - case Opcode.ALOAD_3 : - readLocal(op - Opcode.ALOAD_0); - break; - } - } - - private void doOpcode54_95(CodeIterator ci, int pos, int op) { - switch (op) { - case Opcode.ISTORE : - case Opcode.LSTORE : - case Opcode.FSTORE : - case Opcode.DSTORE : - case Opcode.ASTORE : - writeLocal(ci.byteAt(pos + 1)); - break; - case Opcode.ISTORE_0 : - case Opcode.ISTORE_1 : - case Opcode.ISTORE_2 : - case Opcode.ISTORE_3 : - writeLocal(op - Opcode.ISTORE_0); - break; - case Opcode.LSTORE_0 : - case Opcode.LSTORE_1 : - case Opcode.LSTORE_2 : - case Opcode.LSTORE_3 : - writeLocal(op - Opcode.LSTORE_0); - break; - case Opcode.FSTORE_0 : - case Opcode.FSTORE_1 : - case Opcode.FSTORE_2 : - case Opcode.FSTORE_3 : - writeLocal(op - Opcode.FSTORE_0); - break; - case Opcode.DSTORE_0 : - case Opcode.DSTORE_1 : - case Opcode.DSTORE_2 : - case Opcode.DSTORE_3 : - writeLocal(op - Opcode.DSTORE_0); - break; - case Opcode.ASTORE_0 : - case Opcode.ASTORE_1 : - case Opcode.ASTORE_2 : - case Opcode.ASTORE_3 : - writeLocal(op - Opcode.ASTORE_0); - break; - } - } - - private void doWIDE(CodeIterator ci, int pos) throws BadBytecode { - int op = ci.byteAt(pos + 1); - int var = ci.u16bitAt(pos + 2); - switch (op) { - case Opcode.ILOAD : - case Opcode.LLOAD : - case Opcode.FLOAD : - case Opcode.DLOAD : - case Opcode.ALOAD : - readLocal(var); - break; - case Opcode.ISTORE : - case Opcode.LSTORE : - case Opcode.FSTORE : - case Opcode.DSTORE : - case Opcode.ASTORE : - writeLocal(var); - break; - case Opcode.IINC : - readLocal(var); - // this does not call writeLocal(). - break; - } - } -} diff --git a/src/main/javassist/bytecode/stackmap/MapMaker.java b/src/main/javassist/bytecode/stackmap/MapMaker.java index 37406281..b846661b 100644 --- a/src/main/javassist/bytecode/stackmap/MapMaker.java +++ b/src/main/javassist/bytecode/stackmap/MapMaker.java @@ -16,7 +16,9 @@ package javassist.bytecode.stackmap; +import java.util.ArrayList; import javassist.ClassPool; +import javassist.NotFoundException; import javassist.bytecode.*; /** @@ -125,9 +127,7 @@ public class MapMaker extends Tracer { TypedBlock.getRetType(minfo.getDescriptor())); } - protected MapMaker(MapMaker old, boolean copyStack) { - super(old, copyStack); - } + protected MapMaker(MapMaker old) { super(old); } /** * Runs an analyzer (Phase 1 and 2). @@ -135,34 +135,11 @@ public class MapMaker extends Tracer { void make(TypedBlock[] blocks, byte[] code) throws BadBytecode { - TypedBlock first = blocks[0]; - fixParamTypes(first); - TypeData[] srcTypes = first.localsTypes; - copyFrom(srcTypes.length, srcTypes, this.localsTypes); - make(code, first); - - int n = blocks.length; - for (int i = 0; i < n; i++) - evalExpected(blocks[i]); - } - - /* - * If a parameter type is String but it is used only as Object - * within the method body, this MapMaker class will report its type - * is Object. To avoid this, fixParamTypes calls TypeData.setType() - * on each parameter type. - */ - private void fixParamTypes(TypedBlock first) throws BadBytecode { - TypeData[] types = first.localsTypes; - int n = types.length; - for (int i = 0; i < n; i++) { - TypeData t = types[i]; - if (t instanceof TypeData.ClassName) { - /* Skip the following statement if t.isNullType() is true - * although a parameter type is never null type. - */ - TypeData.setType(t, t.getName(), classPool); - } + make(code, blocks[0]); + try { + fixTypes(blocks); + } catch (NotFoundException e) { + throw new BadBytecode("failed to resolve types", e); } } @@ -171,17 +148,19 @@ public class MapMaker extends Tracer { private void make(byte[] code, TypedBlock tb) throws BadBytecode { - BasicBlock.Catch handlers = tb.toCatch; - while (handlers != null) { - traceException(code, handlers); - handlers = handlers.next; - } + copyTypeData(tb.stackTop, tb.stackTypes, stackTypes); + stackTop = tb.stackTop; + copyTypeData(tb.localsTypes.length, tb.localsTypes, localsTypes); + + traceException(code, tb.toCatch); int pos = tb.position; int end = pos + tb.length; while (pos < end) pos += doOpcode(pos, code); + traceException(code, tb.toCatch); + if (tb.exit != null) { for (int i = 0; i < tb.exit.length; i++) { TypedBlock e = (TypedBlock)tb.exit[i]; @@ -189,7 +168,7 @@ public class MapMaker extends Tracer { mergeMap(e, true); else { recordStackMap(e); - MapMaker maker = new MapMaker(this, true); + MapMaker maker = new MapMaker(this); maker.make(code, e); } } @@ -199,71 +178,66 @@ public class MapMaker extends Tracer { private void traceException(byte[] code, TypedBlock.Catch handler) throws BadBytecode { - TypedBlock tb = (TypedBlock)handler.body; - if (tb.alreadySet()) - mergeMap(tb, false); - else { - recordStackMap(tb, handler.typeIndex); - MapMaker maker = new MapMaker(this, false); - - /* the following code is equivalent to maker.copyFrom(this) - * except stackTypes are not copied. - */ - maker.stackTypes[0] = tb.stackTypes[0].getSelf(); - maker.stackTop = 1; - maker.make(code, tb); + while (handler != null) { + TypedBlock tb = (TypedBlock)handler.body; + if (tb.alreadySet()) + mergeMap(tb, false); + else { + recordStackMap(tb, handler.typeIndex); + MapMaker maker = new MapMaker(this); + maker.make(code, tb); + } + + handler = handler.next; } } - private void mergeMap(TypedBlock dest, boolean mergeStack) { - boolean[] inputs = dest.inputs; - int n = inputs.length; + private void mergeMap(TypedBlock dest, boolean mergeStack) throws BadBytecode { + int n = localsTypes.length; for (int i = 0; i < n; i++) - if (inputs[i]) - merge(localsTypes[i], dest.localsTypes[i]); + dest.localsTypes[i] = merge(localsTypes[i], dest.localsTypes[i]); if (mergeStack) { n = stackTop; for (int i = 0; i < n; i++) - merge(stackTypes[i], dest.stackTypes[i]); + dest.stackTypes[i] = merge(stackTypes[i], dest.stackTypes[i]); } } - private void merge(TypeData td, TypeData target) { - boolean tdIsObj = false; - boolean targetIsObj = false; - // td or target is null if it is TOP. - if (td != TOP && td.isObjectType()) - tdIsObj = true; - - if (target != TOP && target.isObjectType()) - targetIsObj = true; - - if (tdIsObj && targetIsObj) - target.merge(td); + private TypeData merge(TypeData src, TypeData target) throws BadBytecode { + if (src == target) + return target; + else if (target instanceof TypeData.ClassName + || target instanceof TypeData.BasicType) // a parameter + return target; + else if (target instanceof TypeData.AbsTypeVar) { + ((TypeData.AbsTypeVar)target).merge(src); + return target; + } + else + throw new RuntimeException("fatal: this should never happen"); } private void recordStackMap(TypedBlock target) throws BadBytecode { - TypeData[] tStackTypes = new TypeData[stackTypes.length]; + TypeData[] tStackTypes = TypeData.make(stackTypes.length); int st = stackTop; - copyFrom(st, stackTypes, tStackTypes); + recordTypeData(st, stackTypes, tStackTypes); recordStackMap0(target, st, tStackTypes); } private void recordStackMap(TypedBlock target, int exceptionType) throws BadBytecode { + TypeData[] tStackTypes = TypeData.make(stackTypes.length); String type; - if (exceptionType == 0) + if (exceptionType == 0) // for finally clauses type = "java.lang.Throwable"; else type = cpool.getClassInfo(exceptionType); - TypeData[] tStackTypes = new TypeData[stackTypes.length]; tStackTypes[0] = new TypeData.ClassName(type); - recordStackMap0(target, 1, tStackTypes); } @@ -271,34 +245,50 @@ public class MapMaker extends Tracer { throws BadBytecode { int n = localsTypes.length; - TypeData[] tLocalsTypes = new TypeData[n]; - int k = copyFrom(n, localsTypes, tLocalsTypes); - - boolean[] inputs = target.inputs; - for (int i = 0; i < n; i++) - if (!inputs[i]) - tLocalsTypes[i] = TOP; - + TypeData[] tLocalsTypes = TypeData.make(n); + int k = recordTypeData(n, localsTypes, tLocalsTypes); target.setStackMap(st, tStackTypes, k, tLocalsTypes); } - // Phase 2 + protected static int recordTypeData(int n, TypeData[] srcTypes, TypeData[] destTypes) { + int k = -1; + for (int i = 0; i < n; i++) { + TypeData t = srcTypes[i]; + destTypes[i] = t.join(); + if (t != TOP) + k = i + 1; // t might be long or double. + } - void evalExpected(TypedBlock target) throws BadBytecode { - ClassPool cp = classPool; - evalExpected(cp, target.stackTop, target.stackTypes); - TypeData[] types = target.localsTypes; - if (types != null) // unless this block is dead code - evalExpected(cp, types.length, types); + return k + 1; } - private static void evalExpected(ClassPool cp, int n, TypeData[] types) - throws BadBytecode - { - for (int i = 0; i < n; i++) { - TypeData td = types[i]; - if (td != null) - td.evalExpectedType(cp); + protected static void copyTypeData(int n, TypeData[] srcTypes, TypeData[] destTypes) { + for (int i = 0; i < n; i++) + destTypes[i] = srcTypes[i]; + } + + // Phase 2 + + /* + * This method first finds strongly connected components (SCCs) + * on a graph made by TypeData by Tarjan's algorithm. + * SCCs are TypeData nodes sharing the same type. + * Since SCCs are found in the topologically sorted order, + * their types are also fixed when they are found. + */ + private void fixTypes(TypedBlock[] blocks) throws NotFoundException { + ArrayList preOrder = new ArrayList(); + int len = blocks.length; + int index = 0; + for (int i = 0; i < len; i++) { + TypedBlock block = blocks[i]; + int n = block.localsTypes.length; + for (int j = 0; j < n; j++) + index = block.localsTypes[j].dfs(preOrder, index, classPool); + + n = block.stackTop; + for (int j = 0; j < n; j++) + index = block.stackTypes[j].dfs(preOrder, index, classPool); } } @@ -370,19 +360,14 @@ public class MapMaker extends Tracer { } else if (stackTop == 1 && diffL == 0) { TypeData td = bb.stackTypes[0]; - if (td == TOP) - writer.sameLocals(offsetDelta, StackMapTable.TOP, 0); - else - writer.sameLocals(offsetDelta, td.getTypeTag(), - td.getTypeData(cpool)); + writer.sameLocals(offsetDelta, td.getTypeTag(), td.getTypeData(cpool)); return; } else if (stackTop == 2 && diffL == 0) { TypeData td = bb.stackTypes[0]; - if (td != TOP && td.is2WordType()) { + if (td.is2WordType()) { // bb.stackTypes[1] must be TOP. - writer.sameLocals(offsetDelta, td.getTypeTag(), - td.getTypeData(cpool)); + writer.sameLocals(offsetDelta, td.getTypeTag(), td.getTypeData(cpool)); return; } } @@ -401,16 +386,10 @@ public class MapMaker extends Tracer { int j = 0; for (int i = 0; i < num; i++) { TypeData td = types[offset + i]; - if (td == TOP) { - tags[j] = StackMapTable.TOP; - data[j] = 0; - } - else { - tags[j] = td.getTypeTag(); - data[j] = td.getTypeData(cp); - if (td.is2WordType()) - i++; - } + tags[j] = td.getTypeTag(); + data[j] = td.getTypeData(cp); + if (td.is2WordType()) + i++; j++; } @@ -439,14 +418,8 @@ public class MapMaker extends Tracer { private static boolean stackMapEq(TypeData[] oldTd, TypeData[] newTd, int len) { for (int i = 0; i < len; i++) { - TypeData td = oldTd[i]; - if (td == TOP) { // the next element to LONG/DOUBLE is TOP. - if (newTd[i] != TOP) - return false; - } - else - if (!oldTd[i].equals(newTd[i])) - return false; + if (!oldTd[i].eq(newTd[i])) + return false; } return true; @@ -457,7 +430,7 @@ public class MapMaker extends Tracer { while (offset < len) { TypeData td = types[offset++]; num++; - if (td != TOP && td.is2WordType()) + if (td.is2WordType()) offset++; } @@ -515,13 +488,9 @@ public class MapMaker extends Tracer { writer.write16bit(num - numDWord); for (int i = 0; i < num; i++) { TypeData td = types[i]; - if (td == TOP) - writer.writeVerifyTypeInfo(StackMap.TOP, 0); - else { - writer.writeVerifyTypeInfo(td.getTypeTag(), td.getTypeData(cp)); - if (td.is2WordType()) - i++; - } + writer.writeVerifyTypeInfo(td.getTypeTag(), td.getTypeData(cp)); + if (td.is2WordType()) + i++; } } } diff --git a/src/main/javassist/bytecode/stackmap/Tracer.java b/src/main/javassist/bytecode/stackmap/Tracer.java index 7a702bcc..d63fa75a 100644 --- a/src/main/javassist/bytecode/stackmap/Tracer.java +++ b/src/main/javassist/bytecode/stackmap/Tracer.java @@ -31,7 +31,7 @@ import javassist.ClassPool; public abstract class Tracer implements TypeTag { protected ClassPool classPool; protected ConstPool cpool; - protected String returnType; + protected String returnType; // used as the type of ARETURN protected int stackTop; protected TypeData[] stackTypes; @@ -43,39 +43,17 @@ public abstract class Tracer implements TypeTag { cpool = cp; returnType = retType; stackTop = 0; - stackTypes = new TypeData[maxStack]; - localsTypes = new TypeData[maxLocals]; + stackTypes = TypeData.make(maxStack); + localsTypes = TypeData.make(maxLocals); } - public Tracer(Tracer t, boolean copyStack) { + public Tracer(Tracer t) { classPool = t.classPool; cpool = t.cpool; returnType = t.returnType; - stackTop = t.stackTop; - int size = t.stackTypes.length; - stackTypes = new TypeData[size]; - if (copyStack) - copyFrom(t.stackTop, t.stackTypes, stackTypes); - - int size2 = t.localsTypes.length; - localsTypes = new TypeData[size2]; - copyFrom(size2, t.localsTypes, localsTypes); - } - - protected static int copyFrom(int n, TypeData[] srcTypes, TypeData[] destTypes) { - int k = -1; - for (int i = 0; i < n; i++) { - TypeData t = srcTypes[i]; - destTypes[i] = t == TOP ? TOP : t.getSelf(); - if (t != TOP) - if (t.is2WordType()) - k = i + 1; - else - k = i; - } - - return k + 1; + stackTypes = TypeData.make(t.stackTypes.length); + localsTypes = TypeData.make(t.localsTypes.length); } /** @@ -255,11 +233,7 @@ public abstract class Tracer implements TypeTag { case Opcode.AALOAD : { int s = --stackTop - 1; TypeData data = stackTypes[s]; - if (data == null || !data.isObjectType()) - throw new BadBytecode("bad AALOAD"); - else - stackTypes[s] = new TypeData.ArrayElement(data); - + stackTypes[s] = TypeData.ArrayElement.make(data); break; } case Opcode.BALOAD : case Opcode.CALOAD : @@ -309,14 +283,12 @@ public abstract class Tracer implements TypeTag { return 2; } - private int doALOAD(int localVar) { // int localVar, TypeData type) { + private int doALOAD(int localVar) { stackTypes[stackTop++] = localsTypes[localVar]; return 2; } private int doOpcode54_95(int pos, byte[] code, int op) throws BadBytecode { - TypeData[] localsTypes = this.localsTypes; - TypeData[] stackTypes = this.stackTypes; switch (op) { case Opcode.ISTORE : return doXSTORE(pos, code, INTEGER); @@ -376,9 +348,9 @@ public abstract class Tracer implements TypeTag { stackTop -= (op == Opcode.LASTORE || op == Opcode.DASTORE) ? 4 : 3; break; case Opcode.AASTORE : - TypeData.setType(stackTypes[stackTop - 1], - TypeData.ArrayElement.getElementType(stackTypes[stackTop - 3].getName()), - classPool); + TypeData.ArrayElement.aastore(stackTypes[stackTop - 3], + stackTypes[stackTop - 1], + classPool); stackTop -= 3; break; case Opcode.BASTORE : @@ -450,7 +422,7 @@ public abstract class Tracer implements TypeTag { private int doASTORE(int index) { stackTop--; // implicit upcast might be done. - localsTypes[index] = stackTypes[stackTop].copy(); + localsTypes[index] = stackTypes[stackTop]; return 2; } @@ -475,16 +447,16 @@ public abstract class Tracer implements TypeTag { // this does not call writeLocal(). return 3; case Opcode.I2L : - stackTypes[stackTop] = LONG; - stackTypes[stackTop - 1] = TOP; + stackTypes[stackTop - 1] = LONG; + stackTypes[stackTop] = TOP; stackTop++; break; case Opcode.I2F : stackTypes[stackTop - 1] = FLOAT; break; case Opcode.I2D : - stackTypes[stackTop] = DOUBLE; - stackTypes[stackTop - 1] = TOP; + stackTypes[stackTop - 1] = DOUBLE; + stackTypes[stackTop] = TOP; stackTop++; break; case Opcode.L2I : @@ -494,24 +466,26 @@ public abstract class Tracer implements TypeTag { stackTypes[--stackTop - 1] = FLOAT; break; case Opcode.L2D : - stackTypes[stackTop - 1] = DOUBLE; + stackTypes[stackTop - 2] = DOUBLE; break; case Opcode.F2I : stackTypes[stackTop - 1] = INTEGER; break; case Opcode.F2L : - stackTypes[stackTop - 1] = TOP; - stackTypes[stackTop++] = LONG; + stackTypes[stackTop - 1] = LONG; + stackTypes[stackTop] = TOP; + stackTop++; break; case Opcode.F2D : - stackTypes[stackTop - 1] = TOP; - stackTypes[stackTop++] = DOUBLE; + stackTypes[stackTop - 1] = DOUBLE; + stackTypes[stackTop] = TOP; + stackTop++; break; case Opcode.D2I : stackTypes[--stackTop - 1] = INTEGER; break; case Opcode.D2L : - stackTypes[stackTop - 1] = LONG; + stackTypes[stackTop - 2] = LONG; break; case Opcode.D2F : stackTypes[--stackTop - 1] = FLOAT; @@ -602,7 +576,7 @@ public abstract class Tracer implements TypeTag { visitReturn(pos, code); break; case Opcode.ARETURN : - TypeData.setType(stackTypes[--stackTop], returnType, classPool); + stackTypes[--stackTop].setType(returnType, classPool); visitReturn(pos, code); break; case Opcode.RETURN : @@ -644,17 +618,21 @@ public abstract class Tracer implements TypeTag { = new TypeData.ClassName(type); return 3; } case Opcode.ARRAYLENGTH : - TypeData.setType(stackTypes[stackTop - 1], "[Ljava.lang.Object;", classPool); + stackTypes[stackTop - 1].setType("[Ljava.lang.Object;", classPool); stackTypes[stackTop - 1] = INTEGER; break; case Opcode.ATHROW : - TypeData.setType(stackTypes[--stackTop], "java.lang.Throwable", classPool); + stackTypes[--stackTop].setType("java.lang.Throwable", classPool); visitThrow(pos, code); break; case Opcode.CHECKCAST : { // TypeData.setType(stackTypes[stackTop - 1], "java.lang.Object", classPool); int i = ByteArray.readU16bit(code, pos + 1); - stackTypes[stackTop - 1] = new TypeData.ClassName(cpool.getClassInfo(i)); + String type = cpool.getClassInfo(i); + if (type.charAt(0) == '[') + type = type.replace('.', '/'); // getClassInfo() may return "[java.lang.Object;". + + stackTypes[stackTop - 1] = new TypeData.ClassName(type); return 3; } case Opcode.INSTANCEOF : // TypeData.setType(stackTypes[stackTop - 1], "java.lang.Object", classPool); @@ -748,9 +726,9 @@ public abstract class Tracer implements TypeTag { stackTop -= Descriptor.dataSize(desc); char c = desc.charAt(0); if (c == 'L') - TypeData.setType(stackTypes[stackTop], getFieldClassName(desc, 0), classPool); + stackTypes[stackTop].setType(getFieldClassName(desc, 0), classPool); else if (c == '[') - TypeData.setType(stackTypes[stackTop], desc, classPool); + stackTypes[stackTop].setType(desc, classPool); setFieldTarget(notStatic, index); return 3; @@ -767,7 +745,7 @@ public abstract class Tracer implements TypeTag { private void setFieldTarget(boolean notStatic, int index) throws BadBytecode { if (notStatic) { String className = cpool.getFieldrefClassName(index); - TypeData.setType(stackTypes[--stackTop], className, classPool); + stackTypes[--stackTop].setType(className, classPool); } } @@ -823,7 +801,7 @@ public abstract class Tracer implements TypeTag { checkParamTypes(desc, 1); if (notStatic) { String className = cpool.getMethodrefClassName(i); - TypeData.setType(stackTypes[--stackTop], className, classPool); + stackTypes[--stackTop].setType(className, classPool); } pushMemberType(desc); @@ -835,7 +813,7 @@ public abstract class Tracer implements TypeTag { String desc = cpool.getInterfaceMethodrefType(i); checkParamTypes(desc, 1); String className = cpool.getInterfaceMethodrefClassName(i); - TypeData.setType(stackTypes[--stackTop], className, classPool); + stackTypes[--stackTop].setType(className, classPool); pushMemberType(desc); return 5; } @@ -912,10 +890,9 @@ public abstract class Tracer implements TypeTag { stackTop--; if (array) - TypeData.setType(stackTypes[stackTop], - desc.substring(i, k), classPool); + stackTypes[stackTop].setType(desc.substring(i, k), classPool); else if (c == 'L') - TypeData.setType(stackTypes[stackTop], - desc.substring(i + 1, k - 1).replace('/', '.'), classPool); + stackTypes[stackTop].setType(desc.substring(i + 1, k - 1).replace('/', '.'), + classPool); } } diff --git a/src/main/javassist/bytecode/stackmap/TypeData.java b/src/main/javassist/bytecode/stackmap/TypeData.java index cef23211..2b11905f 100644 --- a/src/main/javassist/bytecode/stackmap/TypeData.java +++ b/src/main/javassist/bytecode/stackmap/TypeData.java @@ -20,8 +20,11 @@ import javassist.ClassPool; import javassist.CtClass; import javassist.NotFoundException; import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; import javassist.bytecode.StackMapTable; import javassist.bytecode.BadBytecode; +import java.util.HashSet; +import java.util.Iterator; import java.util.ArrayList; public abstract class TypeData { @@ -29,9 +32,15 @@ public abstract class TypeData { * array type is a subtype of Cloneable and Serializable */ - protected TypeData() {} + public static TypeData[] make(int size) { + TypeData[] array = new TypeData[size]; + for (int i = 0; i < size; i++) + array[i] = TypeTag.TOP; + + return array; + } - public abstract void merge(TypeData neighbor); + protected TypeData() {} /** * Sets the type name of this object type. If the given type name is @@ -40,36 +49,48 @@ public abstract class TypeData { * * @param className dot-separated name unless the type is an array type. */ - static void setType(TypeData td, String className, ClassPool cp) throws BadBytecode { - if (td == TypeTag.TOP) - throw new BadBytecode("unset variable"); - else - td.setType(className, cp); + private static void setType(TypeData td, String className, ClassPool cp) throws BadBytecode { + td.setType(className, cp); } - public abstract boolean equals(Object obj); - public abstract int getTypeTag(); public abstract int getTypeData(ConstPool cp); - /* - * See UninitData.getSelf(). - */ - public TypeData getSelf() { return this; } + public TypeData join() { return new TypeVar(this); } - /* An operand value is copied when it is stored in a - * local variable. + /** + * If the type is a basic type, this method normalizes the type + * and returns a BasicType object. Otherwise, it returns null. */ - public abstract TypeData copy(); + public abstract BasicType isBasicType(); + + public abstract boolean is2WordType(); - public abstract boolean isObjectType(); - public boolean is2WordType() { return false; } + /** + * Returns false if getName() returns a valid type name. + */ public boolean isNullType() { return false; } - public abstract String getName() throws BadBytecode; - protected abstract void setType(String s, ClassPool cp) throws BadBytecode; - public abstract void evalExpectedType(ClassPool cp) throws BadBytecode; - public abstract String getExpected() throws BadBytecode; + public boolean isUninit() { return false; } + + public abstract boolean eq(TypeData d); + + public abstract String getName(); + public abstract void setType(String s, ClassPool cp) throws BadBytecode; + + // depth-first search + public int dfs(ArrayList order, int index, ClassPool cp) + throws NotFoundException + { + return index; + } + + /** + * Returns this if it is a TypeVar or a TypeVar that this + * type depends on. Otherwise, this method returns null. + * It is used by dfs(). + */ + protected TypeVar toTypeVar() { return null; } /** * Primitive types. @@ -83,437 +104,607 @@ public abstract class TypeData { typeTag = tag; } - public void merge(TypeData neighbor) {} - - public boolean equals(Object obj) { - return this == obj; - } - public int getTypeTag() { return typeTag; } public int getTypeData(ConstPool cp) { return 0; } - public boolean isObjectType() { return false; } + public TypeData join() { + if (this == TypeTag.TOP) + return this; + else + return super.join(); + } + + public BasicType isBasicType() { return this; } public boolean is2WordType() { return typeTag == StackMapTable.LONG || typeTag == StackMapTable.DOUBLE; } - public TypeData copy() { - return this; - } - - public void evalExpectedType(ClassPool cp) throws BadBytecode {} - - public String getExpected() throws BadBytecode { - return name; - } + public boolean eq(TypeData d) { return this == d; } public String getName() { return name; } - protected void setType(String s, ClassPool cp) throws BadBytecode { + public void setType(String s, ClassPool cp) throws BadBytecode { throw new BadBytecode("conflict: " + name + " and " + s); } public String toString() { return name; } } - protected static abstract class TypeName extends TypeData { - protected ArrayList equivalences; - - protected String expectedName; - private CtClass cache; - private boolean evalDone; + // a type variable + public static abstract class AbsTypeVar extends TypeData { + public AbsTypeVar() {} + public abstract void merge(TypeData t); + public int getTypeTag() { return StackMapTable.OBJECT; } - protected TypeName() { - equivalences = new ArrayList(); - equivalences.add(this); - expectedName = null; - cache = null; - evalDone = false; + public int getTypeData(ConstPool cp) { + return cp.addClassInfo(getName()); } - public void merge(TypeData neighbor) { - if (this == neighbor) - return; - - if (!(neighbor instanceof TypeName)) - return; // neighbor might be UninitData + public boolean eq(TypeData d) { return getName().equals(d.getName()); } + } - TypeName neighbor2 = (TypeName)neighbor; - ArrayList list = equivalences; - ArrayList list2 = neighbor2.equivalences; - if (list == list2) - return; + // a type variable representing a class type. + public static class TypeVar extends AbsTypeVar { + protected ArrayList lowers; // lower bounds of this type. ArrayList<TypeData> + protected ArrayList usedBy; // reverse relations of lowers + protected ArrayList uppers; // upper bounds of this type. + protected String fixedType; - int n = list2.size(); - for (int i = 0; i < n; i++) { - TypeName tn = (TypeName)list2.get(i); - add(list, tn); - tn.equivalences = list; - } + public TypeVar(TypeData t) { + uppers = null; + lowers = new ArrayList(2); + usedBy = new ArrayList(2); + merge(t); + fixedType = null; } - private static void add(ArrayList list, TypeData td) { - int n = list.size(); - for (int i = 0; i < n; i++) - if (list.get(i) == td) - return; - - list.add(td); + public String getName() { + if (fixedType == null) + return ((TypeData)lowers.get(0)).getName(); + else + return fixedType; } - /* NullType overrides this method. - */ - public int getTypeTag() { return StackMapTable.OBJECT; } - - public int getTypeData(ConstPool cp) { - String type; - try { - type = getExpected(); - } catch (BadBytecode e) { - throw new RuntimeException("fatal error: ", e); - } - - return getTypeData2(cp, type); + public BasicType isBasicType() { + if (fixedType == null) + return ((TypeData)lowers.get(0)).isBasicType(); + else + return null; } - /* NullType overrides this method. - */ - protected int getTypeData2(ConstPool cp, String type) { - return cp.addClassInfo(type); + public boolean is2WordType() { + if (fixedType == null) + return ((TypeData)lowers.get(0)).is2WordType(); + else + return false; } - public boolean equals(Object obj) { - if (obj instanceof TypeName) { - try { - TypeName tn = (TypeName)obj; - return getExpected().equals(tn.getExpected()); - } - catch (BadBytecode e) {} - } - - return false; + public boolean isNullType() { + if (fixedType == null) + return ((TypeData)lowers.get(0)).isNullType(); + else + return false; } - public boolean isObjectType() { return true; } + public boolean isUninit() { + if (fixedType == null) + return ((TypeData)lowers.get(0)).isUninit(); + else + return false; + } - protected void setType(String typeName, ClassPool cp) throws BadBytecode { - if (update(cp, expectedName, typeName)) - expectedName = typeName; + public void merge(TypeData t) { + lowers.add(t); + if (t instanceof TypeVar) + ((TypeVar)t).usedBy.add(this); } - public void evalExpectedType(ClassPool cp) throws BadBytecode { - if (this.evalDone) - return; + public int getTypeTag() { + /* If fixedType is null after calling dfs(), then this + type is NULL, Uninit, or a basic type. So call + getTypeTag() on the first element of lowers. */ + if (fixedType == null) + return ((TypeData)lowers.get(0)).getTypeTag(); + else + return super.getTypeTag(); + } - ArrayList equiv = this.equivalences; - int n = equiv.size(); - String name = evalExpectedType2(equiv, n); - if (name == null) { - name = this.expectedName; - for (int i = 0; i < n; i++) { - TypeData td = (TypeData)equiv.get(i); - if (td instanceof TypeName) { - TypeName tn = (TypeName)td; - if (update(cp, name, tn.expectedName)) - name = tn.expectedName; + public int getTypeData(ConstPool cp) { + if (fixedType == null) + return ((TypeData)lowers.get(0)).getTypeData(cp); + else + return super.getTypeData(cp); + } + + public void setType(String typeName, ClassPool cp) throws BadBytecode { + if (uppers == null) + uppers = new ArrayList(); + + uppers.add(typeName); + } + + protected TypeVar toTypeVar() { return this; } + + private int visited = 0; + private int smallest = 0; + private boolean inList = false; + + // depth-first serach + public int dfs(ArrayList preOrder, int index, ClassPool cp) throws NotFoundException { + if (visited > 0) + return index; // MapMaker.make() may call an already visited node. + + visited = smallest = ++index; + preOrder.add(this); + inList = true; + int n = lowers.size(); + for (int i = 0; i < n; i++) { + TypeVar child = ((TypeData)lowers.get(i)).toTypeVar(); + if (child != null) + if (child.visited == 0) { + index = child.dfs(preOrder, index, cp); + if (child.smallest < smallest) + smallest = child.smallest; + } + else if (child.inList) + if (child.visited < smallest) + smallest = child.visited; + } + + if (visited == smallest) { + ArrayList scc = new ArrayList(); // strongly connected component + TypeVar cv; + do { + cv = (TypeVar)preOrder.remove(preOrder.size() - 1); + cv.inList = false; + scc.add(cv); + } while (cv != this); + fixTypes(scc, cp); + } + + return index; + } + + private void fixTypes(ArrayList scc, ClassPool cp) throws NotFoundException { + HashSet lowersSet = new HashSet(); + boolean isBasicType = false; + TypeData kind = null; + int size = scc.size(); + for (int i = 0; i < size; i++) { + ArrayList tds = ((TypeVar)scc.get(i)).lowers; + int size2 = tds.size(); + for (int j = 0; j < size2; j++) { + TypeData d = (TypeData)tds.get(j); + BasicType bt = d.isBasicType(); + if (kind == null) { + if (bt == null) { + isBasicType = false; + kind = d; + /* If scc has only an UninitData, fixedType is kept null. + So lowerSet must be empty. If scc has not only an UninitData + but also another TypeData, an error must be thrown but this + error detection has not been implemented. */ + if (d.isUninit()) + break; + } + else { + isBasicType = true; + kind = bt; + } } + else { + if ((bt == null && isBasicType) + || (bt != null && kind != bt)) { + isBasicType = true; + kind = TypeTag.TOP; + break; + } + } + + if (bt == null && !d.isNullType()) + lowersSet.add(d.getName()); } } - for (int i = 0; i < n; i++) { - TypeData td = (TypeData)equiv.get(i); - if (td instanceof TypeName) { - TypeName tn = (TypeName)td; - tn.expectedName = name; - tn.cache = null; - tn.evalDone = true; + if (isBasicType) { + for (int i = 0; i < size; i++) { + TypeVar cv = (TypeVar)scc.get(i); + cv.lowers.clear(); + cv.lowers.add(kind); + } + } + else { + String typeName = fixTypes2(scc, lowersSet, cp); + for (int i = 0; i < size; i++) { + TypeVar cv = (TypeVar)scc.get(i); + cv.fixedType = typeName; } } } - private String evalExpectedType2(ArrayList equiv, int n) throws BadBytecode { - String origName = null; - for (int i = 0; i < n; i++) { - TypeData td = (TypeData)equiv.get(i); - if (!td.isNullType()) - if (origName == null) - origName = td.getName(); - else if (!origName.equals(td.getName())) - return null; + private String fixTypes2(ArrayList scc, HashSet lowersSet, ClassPool cp) throws NotFoundException { + Iterator it = lowersSet.iterator(); + if (lowersSet.size() == 0) + return null; // only NullType + else if (lowersSet.size() == 1) + return (String)it.next(); + else { + CtClass cc = cp.get((String)it.next()); + while (it.hasNext()) + cc = commonSuperClassEx(cc, cp.get((String)it.next())); + + if (cc.getSuperclass() == null || isObjectArray(cc)) + cc = fixByUppers(scc, cp, new HashSet(), cc); + + if (cc.isArray()) + return Descriptor.toJvmName(cc); + else + return cc.getName(); } + } - return origName; + private static boolean isObjectArray(CtClass cc) throws NotFoundException { + return cc.isArray() && cc.getComponentType().getSuperclass() == null; } - protected boolean isTypeName() { return true; } + private CtClass fixByUppers(ArrayList users, ClassPool cp, HashSet visited, CtClass type) + throws NotFoundException + { + if (users == null) + return type; - private boolean update(ClassPool cp, String oldName, String typeName) throws BadBytecode { - if (typeName == null) - return false; - else if (oldName == null) - return true; - else if (oldName.equals(typeName)) - return false; - else if (typeName.charAt(0) == '[' - && oldName.equals("[Ljava.lang.Object;")) { - /* this rule is not correct but Tracer class sets the type - of the operand of arraylength to java.lang.Object[]. - Thus, int[] etc. must be a subtype of java.lang.Object[]. - */ - return true; - } + int size = users.size(); + for (int i = 0; i < size; i++) { + TypeVar t = (TypeVar)users.get(i); + if (!visited.add(t)) + return type; - try { - if (cache == null) - cache = cp.get(oldName); - - CtClass cache2 = cp.get(typeName); - if (cache2.subtypeOf(cache)) { - cache = cache2; - return true; + if (t.uppers != null) { + int s = t.uppers.size(); + for (int k = 0; k < s; k++) { + CtClass cc = cp.get((String)t.uppers.get(k)); + if (cc.subtypeOf(type)) + type = cc; + } } - else - return false; - } - catch (NotFoundException e) { - throw new BadBytecode("cannot find " + e.getMessage()); - } - } - /* See also {NullType,ArrayElement}.getExpected(). - */ - public String getExpected() throws BadBytecode { - ArrayList equiv = equivalences; - if (equiv.size() == 1) - return getName(); - else { - String en = expectedName; - if (en == null) - return "java.lang.Object"; - else - return en; + type = fixByUppers(t.usedBy, cp, visited, type); } - } - public String toString() { - try { - String en = expectedName; - if (en != null) - return en; + return type; + } + } - String name = getName(); - if (equivalences.size() == 1) - return name; - else - return name + "?"; - } - catch (BadBytecode e) { - return "<" + e.getMessage() + ">"; - } + /** + * Finds the most specific common super class of the given classes + * by considering array types. + */ + public static CtClass commonSuperClassEx(CtClass one, CtClass two) throws NotFoundException { + if (one.isArray() && two.isArray()) { + CtClass ele1 = one.getComponentType(); + CtClass ele2 = two.getComponentType(); + CtClass element = commonSuperClassEx(ele1, ele2); + if (element == ele1) + return one; + else if (element == ele2) + return two; + + return one.getClassPool().get(element.getName() + "[]"); } + else + return commonSuperClass(one, two); } /** - * Type data for OBJECT. + * Finds the most specific common super class of the given classes. + * This method is a copy from javassist.bytecode.analysis.Type. */ - public static class ClassName extends TypeName { - private String name; // dot separated. null if this object is a copy of another. - - public ClassName(String n) { - name = n; + public static CtClass commonSuperClass(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; } - public TypeData copy() { - return new ClassName(name); + // Phase 2 - Move deepBackup up by (deep end - deep) + for (;;) { + deep = deep.getSuperclass(); + if (deep == null) + break; + + backupDeep = backupDeep.getSuperclass(); } - public String getName() { // never returns null. - return name; + 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; } - /** - * Type data for NULL or OBJECT. - * The types represented by the instances of this class are - * initially NULL but will be OBJECT. + static boolean eq(CtClass one, CtClass two) { + return one == two || (one != null && two != null && one.getName().equals(two.getName())); + } + + public static void aastore(TypeData array, TypeData value, ClassPool cp) throws BadBytecode { + if (array instanceof AbsTypeVar) + if (!value.isNullType()) + ((AbsTypeVar)array).merge(ArrayType.make(value)); + + if (value instanceof AbsTypeVar) + if (array instanceof AbsTypeVar) + ArrayElement.make(array); // should call value.setType() later. + else if (array instanceof ClassName) { + if (!array.isNullType()) { + String type = ArrayElement.typeName(array.getName()); + value.setType(type, cp); + } + } + else + throw new BadBytecode("bad AASTORE: " + array); + } + + /* A type variable representing an array type. + * It is a decorator of another type variable. */ - public static class NullType extends ClassName { - public NullType() { - super("null"); // type name + public static class ArrayType extends AbsTypeVar { + private AbsTypeVar element; + + private ArrayType(AbsTypeVar elementType) { + element = elementType; } - public TypeData copy() { - return new NullType(); + static TypeData make(TypeData element) throws BadBytecode { + if (element instanceof ArrayElement) + return ((ArrayElement)element).arrayType(); + else if (element instanceof AbsTypeVar) + return new ArrayType((AbsTypeVar)element); + else if (element instanceof ClassName) + if (!element.isNullType()) + return new ClassName(typeName(element.getName())); + + throw new BadBytecode("bad AASTORE: " + element); } - public boolean isNullType() { return true; } + public void merge(TypeData t) { + try { + if (!t.isNullType()) + element.merge(ArrayElement.make(t)); + } + catch (BadBytecode e) { + // never happens + throw new RuntimeException("fatal: " + e); + } + } - public int getTypeTag() { - try { - if ("null".equals(getExpected())) - return StackMapTable.NULL; - else - return super.getTypeTag(); - } - catch (BadBytecode e) { - throw new RuntimeException("fatal error: ", e); - } + public String getName() { + return typeName(element.getName()); } - protected int getTypeData2(ConstPool cp, String type) { - if ("null".equals(type)) - return 0; + public AbsTypeVar elementType() { return element; } + + public BasicType isBasicType() { return null; } + public boolean is2WordType() { return false; } + + /* elementType must be a class name. Basic type names + * are not allowed. + */ + public static String typeName(String elementType) { + if (elementType.charAt(0) == '[') + return "[" + elementType; else - return super.getTypeData2(cp, type); + return "[L" + elementType.replace('.', '/') + ";"; } - public String getExpected() throws BadBytecode { - String en = expectedName; - if (en == null) { - // ArrayList equiv = equivalences; - // if (equiv.size() == 1) - // return getName(); - // else - return "java.lang.Object"; - } - else - return en; + public void setType(String s, ClassPool cp) throws BadBytecode { + element.setType(ArrayElement.typeName(s), cp); + } + + protected TypeVar toTypeVar() { return element.toTypeVar(); } + + public int dfs(ArrayList order, int index, ClassPool cp) throws NotFoundException { + return element.dfs(order, index, cp); } } - /** - * Type data for OBJECT if the type is an object type and is - * derived as an element type from an array type by AALOAD. - */ - public static class ArrayElement extends TypeName { - TypeData array; + /* A type variable representing an array-element type. + * It is a decorator of another type variable. + */ + public static class ArrayElement extends AbsTypeVar { + private AbsTypeVar array; - public ArrayElement(TypeData a) { // a is never null + private ArrayElement(AbsTypeVar a) { // a is never null array = a; } - public TypeData copy() { - return new ArrayElement(array); - } + public static TypeData make(TypeData array) throws BadBytecode { + if (array instanceof ArrayType) + return ((ArrayType)array).elementType(); + else if (array instanceof AbsTypeVar) + return new ArrayElement((AbsTypeVar)array); + else if (array instanceof ClassName) + if (!array.isNullType()) + return new ClassName(typeName(array.getName())); - public boolean isNullType() { - return array.isNullType(); + throw new BadBytecode("bad AASTORE: " + array); } - protected void setType(String typeName, ClassPool cp) throws BadBytecode { - super.setType(typeName, cp); - array.setType(getArrayType(typeName), cp); + public void merge(TypeData t) { + try { + if (!t.isNullType()) + array.merge(ArrayType.make(t)); + } + catch (BadBytecode e) { + // never happens + throw new RuntimeException("fatal: " + e); + } } - public String getName() throws BadBytecode { - return getName2(array.getName()); + public String getName() { + return typeName(array.getName()); } - private String getName2(String name) throws BadBytecode { - if (name.length() > 1 && name.charAt(0) == '[') { - char c = name.charAt(1); + public AbsTypeVar arrayType() { return array; } + + /* arrayType must be a class name. Basic type names are + * not allowed. + */ + + public BasicType isBasicType() { return null; } + + public boolean is2WordType() { return false; } + + private static String typeName(String arrayType) { + if (arrayType.length() > 1 && arrayType.charAt(0) == '[') { + char c = arrayType.charAt(1); if (c == 'L') - return name.substring(2, name.length() - 1).replace('/', '.'); + return arrayType.substring(2, arrayType.length() - 1).replace('/', '.'); else if (c == '[') - return name.substring(1); + return arrayType.substring(1); } - if (array.isNullType()) - return "java.lang.Object"; - else - throw new BadBytecode("bad array type for AALOAD: " - + name); + return "java.lang.Object"; // the array type may be NullType } - public String getExpected() throws BadBytecode { - ArrayList equiv = equivalences; - if (equiv.size() == 1) - return getName2(array.getExpected()); // not getName(); - else { - String en = expectedName; - if (en == null) - return "java.lang.Object"; - else - return en; - } + public void setType(String s, ClassPool cp) throws BadBytecode { + array.setType(ArrayType.typeName(s), cp); } - public static String getArrayType(String elementType) { - if (elementType.charAt(0) == '[') - return "[" + elementType; - else - return "[L" + elementType.replace('.', '/') + ";"; + protected TypeVar toTypeVar() { return array.toTypeVar(); } + + public int dfs(ArrayList order, int index, ClassPool cp) throws NotFoundException { + return array.dfs(order, index, cp); } + } - public static String getElementType(String arrayType) { - char c = arrayType.charAt(1); - if (c == 'L') - return arrayType.substring(2, arrayType.length() - 1).replace('/', '.'); - else if (c == '[') - return arrayType.substring(1); - else - return arrayType; + /** + * Type data for OBJECT. + */ + public static class ClassName extends TypeData { + private String name; // dot separated. + + public ClassName(String n) { + name = n; + } + + public String getName() { + return name; + } + + public BasicType isBasicType() { return null; } + + public boolean is2WordType() { return false; } + + public int getTypeTag() { return StackMapTable.OBJECT; } + + public int getTypeData(ConstPool cp) { + return cp.addClassInfo(getName()); + } + + public boolean eq(TypeData d) { return name.equals(d.getName()); } + + public void setType(String typeName, ClassPool cp) throws BadBytecode {} + } + + /** + * Type data for NULL or OBJECT. + * The types represented by the instances of this class are + * initially NULL but will be OBJECT. + */ + public static class NullType extends ClassName { + public NullType() { + super("null-type"); // type name + } + + public int getTypeTag() { + return StackMapTable.NULL; } + + public boolean isNullType() { return true; } + public int getTypeData(ConstPool cp) { return 0; } } /** * Type data for UNINIT. */ - public static class UninitData extends TypeData { - String className; + public static class UninitData extends ClassName { int offset; boolean initialized; UninitData(int offset, String className) { - this.className = className; + super(className); this.offset = offset; this.initialized = false; } - public void merge(TypeData neighbor) {} - - public int getTypeTag() { return StackMapTable.UNINIT; } - public int getTypeData(ConstPool cp) { return offset; } - - public boolean equals(Object obj) { - if (obj instanceof UninitData) { - UninitData ud = (UninitData)obj; - return offset == ud.offset && className.equals(ud.className); - } - else - return false; + public int getTypeTag() { + return StackMapTable.UNINIT; } - public TypeData getSelf() { - if (initialized) - return copy(); - else - return this; + public int getTypeData(ConstPool cp) { + return offset; } - public TypeData copy() { - return new ClassName(className); + public TypeData join() { + TypeData td = initialized ? new ClassName(getName()) : this; + return new TypeVar(td); } - public boolean isObjectType() { return true; } + public boolean isUninit() { return true; } - protected void setType(String typeName, ClassPool cp) throws BadBytecode { - initialized = true; + public boolean eq(TypeData d) { + if (d instanceof UninitData) { + UninitData ud = (UninitData)d; + return offset == ud.offset && getName().equals(ud.getName()); + } + else + return false; } - public void evalExpectedType(ClassPool cp) throws BadBytecode {} - - public String getName() { - return className; + public void setType(String typeName, ClassPool cp) throws BadBytecode { + super.setType(typeName, cp); + initialized = true; } - public String getExpected() { return className; } - - public String toString() { return "uninit:" + className + "@" + offset; } + public String toString() { return "uninit:" + getName() + "@" + offset; } } public static class UninitThis extends UninitData { @@ -521,11 +712,12 @@ public abstract class TypeData { super(-1, className); } - public int getTypeTag() { return StackMapTable.THIS; } - public int getTypeData(ConstPool cp) { return 0; } + public int getTypeTag() { + return StackMapTable.THIS; + } - public boolean equals(Object obj) { - return obj instanceof UninitThis; + public int getTypeData(ConstPool cp) { + return 0; } public String toString() { return "uninit:this"; } diff --git a/src/main/javassist/bytecode/stackmap/TypeTag.java b/src/main/javassist/bytecode/stackmap/TypeTag.java index 963ecc18..2a90d147 100644 --- a/src/main/javassist/bytecode/stackmap/TypeTag.java +++ b/src/main/javassist/bytecode/stackmap/TypeTag.java @@ -19,7 +19,8 @@ package javassist.bytecode.stackmap; import javassist.bytecode.StackMapTable; public interface TypeTag { - TypeData TOP = null; + String TOP_TYPE = "*top*"; + TypeData TOP = new TypeData.BasicType(TOP_TYPE, StackMapTable.TOP); TypeData INTEGER = new TypeData.BasicType("int", StackMapTable.INTEGER); TypeData FLOAT = new TypeData.BasicType("float", StackMapTable.FLOAT); TypeData DOUBLE = new TypeData.BasicType("double", StackMapTable.DOUBLE); diff --git a/src/main/javassist/bytecode/stackmap/TypedBlock.java b/src/main/javassist/bytecode/stackmap/TypedBlock.java index f589a6ee..5ecada13 100644 --- a/src/main/javassist/bytecode/stackmap/TypedBlock.java +++ b/src/main/javassist/bytecode/stackmap/TypedBlock.java @@ -20,16 +20,10 @@ import javassist.bytecode.*; public class TypedBlock extends BasicBlock { public int stackTop, numLocals; - public TypeData[] stackTypes, localsTypes; - - // set by a Liveness object. - // inputs[i] is true if the i-th variable is used within this block. - public boolean[] inputs; - - // working area for Liveness class. - public boolean updating; - public int status; - public byte[] localsUsage; + // localsTypes is set to non-null when this block is first visited by a MapMaker. + // see alreadySet(). + public TypeData[] localsTypes; + public TypeData[] stackTypes; /** * Divides the method body into basic blocks. @@ -52,16 +46,12 @@ public class TypedBlock extends BasicBlock { blocks[0].initFirstBlock(ca.getMaxStack(), ca.getMaxLocals(), pool.getClassName(), minfo.getDescriptor(), isStatic, minfo.isConstructor()); - new Liveness().compute(ca.iterator(), blocks, ca.getMaxLocals(), - blocks[0].localsTypes); return blocks; } protected TypedBlock(int pos) { super(pos); localsTypes = null; - inputs = null; - updating = false; } protected void toString2(StringBuffer sbuf) { @@ -70,11 +60,6 @@ public class TypedBlock extends BasicBlock { printTypes(sbuf, stackTop, stackTypes); sbuf.append("}, locals={"); printTypes(sbuf, numLocals, localsTypes); - sbuf.append("}, inputs={"); - if (inputs != null) - for (int i = 0; i < inputs.length; i++) - sbuf.append(inputs[i] ? "1, " : "0, "); - sbuf.append('}'); } @@ -111,10 +96,9 @@ public class TypedBlock extends BasicBlock { public void resetNumLocals() { if (localsTypes != null) { int nl = localsTypes.length; - while (nl > 0 && localsTypes[nl - 1] == TypeTag.TOP) { + while (nl > 0 && localsTypes[nl - 1].isBasicType() == TypeTag.TOP) { if (nl > 1) { - TypeData td = localsTypes[nl - 2]; - if (td == TypeTag.LONG || td == TypeTag.DOUBLE) + if (localsTypes[nl - 2].is2WordType()) break; } @@ -153,8 +137,8 @@ public class TypedBlock extends BasicBlock { throw new BadBytecode("no method descriptor: " + methodDesc); stackTop = 0; - stackTypes = new TypeData[maxStack]; - TypeData[] locals = new TypeData[maxLocals]; + stackTypes = TypeData.make(maxStack); + TypeData[] locals = TypeData.make(maxLocals); if (isConstructor) locals[0] = new TypeData.UninitThis(className); else if (!isStatic) diff --git a/src/test/javassist/JvstTest.java b/src/test/javassist/JvstTest.java index 16d91390..4e22dbc1 100644 --- a/src/test/javassist/JvstTest.java +++ b/src/test/javassist/JvstTest.java @@ -1110,7 +1110,7 @@ public class JvstTest extends JvstTestRoot { suite.addTestSuite(JvstTest4.class); suite.addTestSuite(LoaderTestByRandall.class); suite.addTestSuite(javassist.bytecode.BytecodeTest.class); - // suite.addTestSuite(javassist.bytecode.StackMapTest.class); + suite.addTestSuite(javassist.bytecode.StackMapTest.class); suite.addTestSuite(javassist.compiler.CompTest.class); suite.addTestSuite(javassist.SetterTest.class); suite.addTestSuite(javassist.bytecode.InsertGap0.class); diff --git a/src/test/javassist/JvstTest4.java b/src/test/javassist/JvstTest4.java index f06d79f3..3ff6927d 100644 --- a/src/test/javassist/JvstTest4.java +++ b/src/test/javassist/JvstTest4.java @@ -683,9 +683,10 @@ public class JvstTest4 extends JvstTestRoot { } } System.gc(); + System.gc(); int size = javassist.compiler.MemberResolver.getInvalidMapSize(); System.out.println("JIRA150b " + size); - assertTrue(size < N - 10); + assertTrue("JIRA150b size: " + size, size < N - 10); } public void testJIRA152() throws Exception { diff --git a/src/test/javassist/bytecode/StackMapTest.java b/src/test/javassist/bytecode/StackMapTest.java new file mode 100644 index 00000000..bd21a899 --- /dev/null +++ b/src/test/javassist/bytecode/StackMapTest.java @@ -0,0 +1,315 @@ +package javassist.bytecode; + +import java.util.Iterator; +import java.util.List; +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.lang.reflect.Method; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.CtNewMethod; +import javassist.JvstTest; +import javassist.Loader; +import javassist.bytecode.stackmap.MapMaker; +import javassist.bytecode.stackmap.TypeData; +import junit.framework.TestCase; + +public class StackMapTest extends TestCase { + public static final String PATH = JvstTest.PATH; + private ClassPool loader, dloader; + private Loader cloader; + + public StackMapTest(String name) { super(name); } + + protected void setUp() throws Exception { + loader = ClassPool.getDefault(); + dloader = new ClassPool(null); + dloader.appendSystemPath(); + dloader.insertClassPath("."); + cloader = new Loader(dloader); + } + + protected Object make(String name) throws Exception { + return cloader.loadClass(name).newInstance(); + } + + protected int invoke(Object target, String method) throws Exception { + Method m = target.getClass().getMethod(method, new Class[0]); + Object res = m.invoke(target, new Object[0]); + return ((Integer)res).intValue(); + } + + protected static void rebuildStackMaps(CtClass cc) throws BadBytecode, IOException { + ClassPool cp = cc.getClassPool(); + Iterator it = cc.getClassFile().getMethods().iterator(); + while (it.hasNext()) { + MethodInfo minfo = (MethodInfo)it.next(); + rebuildStackMap(cc, cp, minfo); + } + } + + protected static void rebuildStackMap(CtClass cc, ClassPool cp, MethodInfo minfo) throws BadBytecode, IOException { + CodeAttribute ca = minfo.getCodeAttribute(); + if (ca != null) { + StackMapTable smt = (StackMapTable)ca.getAttribute(StackMapTable.tag); + if (smt != null) { + String data = readSmt(smt); + StackMapTable smt2 = MapMaker.make(cp, minfo); + String data2 = readSmt(smt2); + try { + assertEquals(cc.getName() + ":" + minfo.getName() + ":" + minfo.getDescriptor(), + data, data2); + } + catch (junit.framework.ComparisonFailure e) { + System.out.println("*** " + cc.getName() + ":" + minfo.getName() + ":" + minfo.getDescriptor()); + smt.println(System.out); + System.out.println("---"); + smt2.println(System.out); + } + } + } + } + + protected static void rebuildStackMaps2(CtClass cc) throws BadBytecode { + ClassPool cp = cc.getClassPool(); + Iterator it = cc.getClassFile().getMethods().iterator(); + while (it.hasNext()) { + MethodInfo minfo = (MethodInfo)it.next(); + minfo.rebuildStackMap(cp); + } + } + + protected static String readSmt(StackMapTable smt) throws BadBytecode, IOException { + if (smt == null) + return ""; + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + PrintStream pout = new PrintStream(out); + smt.println(pout); + pout.close(); + return out.toString(); + } + + public static void main(String[] args) throws Exception { + if (args.length == 2 && args[0].equals("-c")) { + CtClass cc = ClassPool.getDefault().get(args[0].replace('/', '.')); + rebuildStackMaps(cc); + } + else { + for (int i = 0; i < args.length; i++) { + CtClass cc = ClassPool.getDefault().get(getClassName(args[i])); + System.out.println(cc.getName()); + rebuildStackMaps2(cc); + cc.writeFile("rebuild"); + } + } + } + + public static String getClassName(String fileName) { + Matcher m = Pattern.compile("(.*)\\.class").matcher(fileName); + if (m.matches()) + return m.group(1).replace('/', '.'); + else + return fileName; + } + + public void testCommonSuper() throws Exception { + CtClass type = loader.get("javassist.CtClassType[]"); + CtClass base = loader.get("javassist.CtClass[]"); + CtClass array = loader.get("javassist.CtArray[]"); + CtClass array2 = loader.get("javassist.CtArray[][]"); + CtClass str = loader.get("java.lang.String[]"); + CtClass strObj = loader.get("java.lang.String"); + CtClass obj = loader.get("java.lang.Object[]"); + CtClass objObj = loader.get("java.lang.Object"); + + assertEquals(base, TypeData.commonSuperClassEx(type, base)); + assertEquals(base, TypeData.commonSuperClassEx(base, type)); + assertEquals(base, TypeData.commonSuperClassEx(array, type)); + assertEquals(obj, TypeData.commonSuperClassEx(base, str)); + assertEquals(objObj, TypeData.commonSuperClassEx(strObj, str)); + assertEquals(obj, TypeData.commonSuperClassEx(array, array2)); + assertEquals(obj, TypeData.commonSuperClassEx(base, array2)); + } + + public void testRebuild() throws Exception { + CtClass cc = loader.get("javassist.bytecode.StackMapTest$T1"); + rebuildStackMaps2(cc); + //Class c = cc.toClass(); + //Object t1 = c.newInstance(); + cc.writeFile(); + Object t1 = make(cc.getName()); + assertEquals(3, invoke(t1, "test")); + } + + public static interface Intf { + int foo(); + } + + public static class C1 implements Intf { + public int foo() { return 0; } + } + + public static class C2 implements Intf { + public int foo() { return 3; } + } + + public static class T1 { + public int test() { + return foo(-1); + } + + public int foo(int i) { + Intf obj; + if (i > 0) + obj = new C1(); + else + obj = new C2(); + + return obj.foo(); + } + } + + public void testRebuild2() throws Exception { + CtClass cc = loader.get("javassist.bytecode.StackMapTest$C3"); + rebuildStackMaps2(cc); + cc.writeFile(); + Object t1 = make(cc.getName()); + assertEquals(7, invoke(t1, "test")); + } + + public static class C3 { + int value; + public C3(int i) { + value = i; + } + public C3(boolean b) { + this(b ? 7 : 10); + } + public C3() { this(true); } + public int test() { return value; } + } + + public void testRebuild3() throws Exception { + CtClass cc = loader.get("javassist.bytecode.StackMapTest$T3"); + rebuildStackMaps2(cc); + cc.writeFile(); + Object t1 = make(cc.getName()); + assertEquals(1100, invoke(t1, "test")); + } + + public static interface Intf2 { + int bar(); + } + + public static class D1 extends C1 implements Intf2 { + public int bar() { return 10; } + } + + public static class D2 extends C1 implements Intf2 { + public int bar() { return 100; } + } + + public static class T3 { + public int bar(Intf2 i) { return 1000; } + + public int test() { + return foo(-1); + } + + public int foo(int i) { + Intf2 obj; + if (i > 0) + obj = new D1(); + else + obj = new D2(); + + System.out.println(obj.toString()); + return obj.bar() + bar(obj); + } + } + + public void testRebuildArray() throws Exception { + CtClass cc = loader.get("javassist.bytecode.StackMapTest$T4"); + rebuildStackMaps2(cc); + cc.writeFile(); + Object t1 = make(cc.getName()); + assertEquals(30, invoke(t1, "test")); + } + + public static class T4 { + public int test() { + return foo(3) + foo2(3) + foo3(3) + foo4(3); + } + + public int foo(int i) { + C1[] a; + if (i > 0) + a = new D1[1]; + else + a = new D2[1]; + + if (i > 0) + a[0] = new D1(); + else + a[0] = new D2(); + + return a[0].foo(); + } + + public int foo2(int i) { + Intf2[] a; + if (i > 0) + a = new D1[1]; + else + a = new D2[1]; + + if (i > 0) + a[0] = new D1(); + else + a[0] = new D2(); + + return a[0].bar(); + } + + public int foo3(int i) { + Intf2[] a = null; + if (i > 0) + a = new D1[1]; + + a[0] = new D1(); + return a[0].bar(); + } + + public int foo4(int i) { + Intf2[] a = new Intf2[1]; + if (i > 0) + a[0] = new D1(); + return a[0].bar(); + } + } + + public void tstCtClassType() throws Exception { + ClassPool cp = ClassPool.getDefault(); + CtClass cc = cp.get("javassist.CtClassType"); + MethodInfo minfo = getMethodInfo(cc.getClassFile(), "getFields", "(Ljava/util/ArrayList;Ljavassist/CtClass;)V"); + rebuildStackMap(cc, cp, minfo); + } + + MethodInfo getMethodInfo(ClassFile cf, String name, String desc) { + List list = cf.getMethods(); + Iterator it = list.iterator(); + while (it.hasNext()) { + MethodInfo mi = (MethodInfo)it.next(); + if (mi.getName().equals(name) && mi.getDescriptor().equals(desc)) + return mi; + } + + return null; + } +} diff --git a/src/test/javassist/tools/reflect/LoaderTest.java b/src/test/javassist/tools/reflect/LoaderTest.java index d11a38a8..f96feb7b 100644 --- a/src/test/javassist/tools/reflect/LoaderTest.java +++ b/src/test/javassist/tools/reflect/LoaderTest.java @@ -53,6 +53,10 @@ public class LoaderTest extends TestCase { "javassist.tools.reflect.ClassMetaobject"); ClassPool cp = ClassPool.getDefault(); + + CtClass cc2 = cp.get("javassist.tools.reflect.SuperClass"); + cc2.debugWriteFile("reflected/"); + CtClass cc = cp.get("javassist.tools.reflect.SubClass"); CtMethod[] ms = cc.getMethods(); |