From 10c21b809410d9c2b164bf5d88ec12a9787e4590 Mon Sep 17 00:00:00 2001 From: chiba Date: Tue, 10 Apr 2007 16:32:32 +0000 Subject: [PATCH] stackmap support (cont.) git-svn-id: http://anonsvn.jboss.org/repos/javassist/trunk@359 30ef5769-5b8d-40dd-aea6-55b5d6557bb3 --- .../javassist/bytecode/CodeAttribute.java | 14 + .../javassist/bytecode/StackMapTable.java | 34 +- .../bytecode/stackmap/BasicBlock.java | 358 +++++++++++--- .../javassist/bytecode/stackmap/MapMaker.java | 433 ++++++++++++++++ .../bytecode/stackmap/StackAnalyzer.java | 10 - .../{StackAnalyzerCore.java => Tracer.java} | 468 ++++++++++-------- .../javassist/bytecode/stackmap/TypeData.java | 467 +++++++++++++++++ .../javassist/bytecode/stackmap/TypeTag.java | 28 ++ 8 files changed, 1514 insertions(+), 298 deletions(-) create mode 100644 src/main/javassist/bytecode/stackmap/MapMaker.java delete mode 100644 src/main/javassist/bytecode/stackmap/StackAnalyzer.java rename src/main/javassist/bytecode/stackmap/{StackAnalyzerCore.java => Tracer.java} (64%) create mode 100644 src/main/javassist/bytecode/stackmap/TypeData.java create mode 100644 src/main/javassist/bytecode/stackmap/TypeTag.java diff --git a/src/main/javassist/bytecode/CodeAttribute.java b/src/main/javassist/bytecode/CodeAttribute.java index 83a0a429..91f634b3 100644 --- a/src/main/javassist/bytecode/CodeAttribute.java +++ b/src/main/javassist/bytecode/CodeAttribute.java @@ -292,6 +292,20 @@ public class CodeAttribute extends AttributeInfo implements Opcode { return AttributeInfo.lookup(attributes, name); } + /** + * Adds a stack map table. If another copy of stack map table + * is already contained, the old one is removed. + * + * @param smt the stack map table added to this code attribute. + * If it is null, a new stack map is not added. + * Only the old stack map is removed. + */ + public void setAttribute(StackMapTable smt) { + AttributeInfo.remove(attributes, StackMapTable.tag); + if (smt != null) + attributes.add(smt); + } + /** * Copies code. */ diff --git a/src/main/javassist/bytecode/StackMapTable.java b/src/main/javassist/bytecode/StackMapTable.java index cb59efa2..7b4b9bf3 100644 --- a/src/main/javassist/bytecode/StackMapTable.java +++ b/src/main/javassist/bytecode/StackMapTable.java @@ -41,7 +41,7 @@ public class StackMapTable extends AttributeInfo { /** * Constructs a stack_map attribute. */ - private StackMapTable(ConstPool cp, byte[] newInfo) { + StackMapTable(ConstPool cp, byte[] newInfo) { super(cp, tag, newInfo); } @@ -143,6 +143,9 @@ public class StackMapTable extends AttributeInfo { /** * Constructs a walker. + * + * @param smt the StackMapTable that this walker + * walks around. */ public Walker(StackMapTable smt) { this(smt.get()); @@ -156,7 +159,7 @@ public class StackMapTable extends AttributeInfo { * It can be obtained by get() * in the AttributeInfo class. */ - Walker(byte[] data) { + public Walker(byte[] data) { info = data; numOfEntries = ByteArray.readU16bit(data, 0); } @@ -447,6 +450,17 @@ public class StackMapTable extends AttributeInfo { return b; } + /** + * Constructs and a return a stack map table containing + * the written stack map entries. + * + * @param cp the constant pool used to write + * the stack map entries. + */ + public StackMapTable toStackMapTable(ConstPool cp) { + return new StackMapTable(cp, toByteArray()); + } + /** * Writes a same_frame or a same_frame_extended. */ @@ -559,6 +573,22 @@ public class StackMapTable extends AttributeInfo { } } + /** + * Prints the stack table map. + */ + public void println(PrintWriter w) { + Printer.print(this, w); + } + + /** + * Prints the stack table map. + * + * @param ps a print stream such as System.out. + */ + public void println(java.io.PrintStream ps) { + Printer.print(this, new java.io.PrintWriter(ps, true)); + } + static class Printer extends Walker { private PrintWriter writer; diff --git a/src/main/javassist/bytecode/stackmap/BasicBlock.java b/src/main/javassist/bytecode/stackmap/BasicBlock.java index 4f0111bf..dbce4e6c 100644 --- a/src/main/javassist/bytecode/stackmap/BasicBlock.java +++ b/src/main/javassist/bytecode/stackmap/BasicBlock.java @@ -1,55 +1,113 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2006 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. + * + * 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.*; +import java.util.ArrayList; + +public class BasicBlock implements TypeTag, Comparable { + + public int position, length; + public int stackTop, numLocals; + public TypeData[] stackTypes, localsTypes; + + /* The number of the basic blocks from which a thread of control + * may reach this basic block. The number excludes the preceding + * block. Thus, if it is zero, a thread of control reaches + * only from the preceding block. Such a basic block represents + * the boundary of a try block. + */ + public int inbound; -public class BasicBlock { - public int position; - public int stackTop; - public int[] stackTypes, localsTypes; - public Object[] stackData, localsData; + /* public static void main(String[] args) throws Exception { + BasicBlock b = new BasicBlock(0); + b.initFirstBlock(8, 1, args[0], args[1], args[2].equals("static"), args[2].equals("const")); + System.out.println(b); + }*/ private BasicBlock(int pos) { position = pos; + length = 0; + stackTop = numLocals = 0; + stackTypes = localsTypes = null; + inbound = 1; } - public void set(int st, int[] stypes, Object[] sdata, int[] ltypes, Object[] ldata) + public boolean alreadySet() { return stackTypes != null; } + + /* + * Computes the correct value of numLocals. + * It assumes that: + * correct numLocals <= current numLocals + */ + public void resetNumLocals() { + if (localsTypes != null) { + int nl = numLocals; + while (nl > 0 && localsTypes[nl - 1] == TypeTag.TOP) + --nl; + + numLocals = nl; + } + } + + public void setStackMap(int st, TypeData[] stack, + int nl, TypeData[] locals) throws BadBytecode { - if (stackTypes == null) { - stackTop = st; - stackTypes = copy(stypes); - stackData = copy(sdata); - localsTypes = copy(ltypes); - localsData = copy(ldata); - } - else { - if (st != stackTop) - throw new BadBytecode("verification failure"); - - int n = ltypes.length; - for (int i = 0; i < n; i++) - if (ltypes[i] != localsTypes[i]) { - localsTypes[i] = StackAnalyzerCore.EMPTY; - localsData[i] = null; - } - else if (ltypes[i] == StackAnalyzerCore.OBJECT - && !ldata[i].equals(localsData[i])) - ; // localsData[i] = ??; - } + stackTop = st; + stackTypes = stack; + numLocals = nl; + localsTypes = locals; } - private static int[] copy(int[] a) { - int[] b = new int[a.length]; - System.arraycopy(a, 0, b, 0, a.length); - return b; + private void updateLength(int nextPos) { + length = nextPos - position; } - private static Object[] copy(Object[] a) { - Object[] b = new Object[a.length]; - System.arraycopy(a, 0, b, 0, a.length); - return b; + public String toString() { + StringBuffer sbuf = new StringBuffer(); + sbuf.append("Block at "); + sbuf.append(position); + sbuf.append(" stack={"); + printTypes(sbuf, stackTop, stackTypes); + sbuf.append("} locals={"); + printTypes(sbuf, numLocals, localsTypes); + sbuf.append('}'); + return sbuf.toString(); } + private static void printTypes(StringBuffer sbuf, int size, + TypeData[] types) { + if (types == null) + return; + + for (int i = 0; i < size; i++) { + if (i > 0) + sbuf.append(", "); + + TypeData td = types[i]; + sbuf.append(td == null ? "<>" : td.toString()); + } + } + + /** + * Finds the basic block including the given position. + * + * @param pos the position. + */ public static BasicBlock find(BasicBlock[] blocks, int pos) throws BadBytecode { int n = blocks.length; for (int i = 0; i < n; i++) @@ -59,102 +117,254 @@ public class BasicBlock { throw new BadBytecode("no basic block: " + pos); } - public static BasicBlock[] makeBlocks(CodeIterator ci, ExceptionTable et) + /** + * Divides the given code fragment into basic blocks. + */ + public static BasicBlock[] makeBlocks(MethodInfo minfo) throws BadBytecode { + CodeAttribute ca = minfo.getCodeAttribute(); + CodeIterator ci = ca.iterator(); + ConstPool pool = minfo.getConstPool(); + BasicBlock[] blocks = makeBlocks(ci, 0, ci.getCodeLength(), ca.getExceptionTable(), 0, pool); + boolean isStatic = (minfo.getAccessFlags() & AccessFlag.STATIC) != 0; + blocks[0].initFirstBlock(ca.getMaxStack(), ca.getMaxLocals(), + pool.getClassName(), minfo.getDescriptor(), + isStatic, minfo.isConstructor()); + return blocks; + } + + /** + * Divides the given code fragment into basic blocks. + * + * @param begin the position where the basic block analysis starts. + * @param end exclusive. + * @param et the appended exception table entries. + * @param etOffset the offset added to the handlerPc entries in the exception table. + * @param pool the constant pool. + */ + public static BasicBlock[] makeBlocks(CodeIterator ci, int begin, int end, + ExceptionTable et, int etOffset, ConstPool pool) throws BadBytecode { ci.begin(); - int[] targets = new int[16]; - int size = 0; + ci.move(begin); + ArrayList targets = new ArrayList(); + targets.add(new BasicBlock(begin)); while (ci.hasNext()) { int index = ci.next(); + if (index >= end) + break; + int op = ci.byteAt(index); if ((Opcode.IFEQ <= op && op <= Opcode.IF_ACMPNE) || op == Opcode.IFNULL || op == Opcode.IFNONNULL) - targets = add(targets, size++, index + ci.s16bitAt(index + 1)); + targets.add(new BasicBlock(index + ci.s16bitAt(index + 1))); else if (Opcode.GOTO <= op && op <= Opcode.LOOKUPSWITCH) switch (op) { case Opcode.GOTO : - targets = add(targets, size++, index + ci.s16bitAt(index + 1)); - break; case Opcode.JSR : - case Opcode.RET : - throw new BadBytecode("jsr/ret at " + index); + targets.add(new BasicBlock(index + ci.s16bitAt(index + 1))); + break; + // case Opcode.RET : + // throw new BadBytecode("ret at " + index); case Opcode.TABLESWITCH : { int pos = (index & ~3) + 4; - targets = add(targets, size++, index + ci.s32bitAt(pos)); // default offset + targets.add(new BasicBlock(index + ci.s32bitAt(pos))); // default branch target int low = ci.s32bitAt(pos + 4); int high = ci.s32bitAt(pos + 8); int p = pos + 12; int n = p + (high - low + 1) * 4; while (p < n) { - targets = add(targets, size++, index + ci.s32bitAt(p)); + targets.add(new BasicBlock(index + ci.s32bitAt(p))); p += 4; } break; } case Opcode.LOOKUPSWITCH : { int pos = (index & ~3) + 4; - targets = add(targets, size++, index + ci.s32bitAt(pos)); // default offset + targets.add(new BasicBlock(index + ci.s32bitAt(pos))); // default branch target int p = pos + 8 + 4; int n = p + ci.s32bitAt(pos + 4) * 8; while (p < n) { - targets = add(targets, size++, index + ci.s32bitAt(p)); + targets.add(new BasicBlock(index + ci.s32bitAt(p))); p += 8; } break; } } - else if (op == Opcode.GOTO_W) - targets = add(targets, size++, index + ci.s32bitAt(index + 1)); - else if (op == Opcode.JSR_W) - throw new BadBytecode("jsr_w at " + index); + else if (op == Opcode.GOTO_W || op == Opcode.JSR_W) + targets.add(new BasicBlock(index + ci.s32bitAt(index + 1))); } if (et != null) { int i = et.size(); while (--i >= 0) { - targets = add(targets, size++, et.startPc(i)); - targets = add(targets, size++, et.handlerPc(i)); + BasicBlock bb = new BasicBlock(et.startPc(i) + etOffset); + bb.inbound = 0; + targets.add(bb); + targets.add(new BasicBlock(et.handlerPc(i) + etOffset)); } } - return trimArray(targets); + return trimArray(targets, end); } - private static int[] add(int[] targets, int size, int value) { - if (targets.length >= size) { - int[] a = new int[size << 1]; - System.arraycopy(targets, 0, a, 0, targets.length); - targets = a; + public int compareTo(Object obj) { + if (obj instanceof BasicBlock) { + int pos = ((BasicBlock)obj).position; + return position - pos; } - targets[size++] = value; - return targets; + return -1; } - private static BasicBlock[] trimArray(int[] targets) { - int size = targets.length; - java.util.Arrays.sort(targets); + /** + * @param endPos exclusive + */ + private static BasicBlock[] trimArray(ArrayList targets, int endPos) { + Object[] targetArray = targets.toArray(); + int size = targetArray.length; + java.util.Arrays.sort(targetArray); int s = 0; - int t0 = 0; + int t0 = -1; for (int i = 0; i < size; i++) { - int t = targets[i]; + int t = ((BasicBlock)targetArray[i]).position; if (t != t0) { s++; t0 = t; } } - BasicBlock[] results = new BasicBlock[s + 1]; - results[0] = new BasicBlock(0); - t0 = 0; - for (int i = 0, j = 1; i < size; i++) { - int t = targets[i]; - if (t != t0) { - BasicBlock b = new BasicBlock(t); - results[j++] = b; + BasicBlock[] results = new BasicBlock[s]; + BasicBlock bb0 = (BasicBlock)targetArray[0]; + results[0] = bb0; + t0 = bb0.position; + int j = 1; + for (int i = 1; i < size; i++) { + BasicBlock bb = (BasicBlock)targetArray[i]; + int t = bb.position; + if (t == t0) + results[j - 1].inbound += bb.inbound; + else { + results[j - 1].updateLength(t); + results[j++] = bb; t0 = t; } } + results[j - 1].updateLength(endPos); return results; } + + /** + * Initializes the first block by the given method descriptor. + * + * @param block the first basic block that this method initializes. + * @param className a dot-separated fully qualified class name. + * For example, javassist.bytecode.stackmap.BasicBlock. + * @param methodDesc method descriptor. + * @param isStatic true if the method is a static method. + * @param isConstructor true if the method is a constructor. + */ + void initFirstBlock(int maxStack, int maxLocals, String className, + String methodDesc, boolean isStatic, boolean isConstructor) + throws BadBytecode + { + if (methodDesc.charAt(0) != '(') + throw new BadBytecode("no method descriptor: " + methodDesc); + + stackTop = 0; + stackTypes = new TypeData[maxStack]; + TypeData[] locals = new TypeData[maxLocals]; + if (isConstructor) + locals[0] = new TypeData.UninitThis(className); + else if (!isStatic) + locals[0] = new TypeData.ClassName(className); + + int n = isStatic ? -1 : 0; + int i = 1; + do { + try { + i = descToTag(methodDesc, i, ++n, locals); + } + catch (StringIndexOutOfBoundsException e) { + throw new BadBytecode("bad method descriptor: " + + methodDesc); + } + } while (i > 0); + + numLocals = n; + localsTypes = locals; + position = 0; + inbound = 0; + } + + private static int descToTag(String desc, int i, + int n, TypeData[] types) + throws BadBytecode + { + int i0 = i; + int arrayDim = 0; + char c = desc.charAt(i); + if (c == ')') + return 0; + + while (c == '[') { + ++arrayDim; + c = desc.charAt(++i); + } + + if (c == 'L') { + int i2 = desc.indexOf(';', ++i); + if (arrayDim > 0) + types[n] = new TypeData.ClassName(desc.substring(i0, ++i2)); + else + types[n] = new TypeData.ClassName(desc.substring(i0 + 1, ++i2 - 1) + .replace('/', '.')); + return i2; + } + else if (arrayDim > 0) { + types[n] = new TypeData.ClassName(desc.substring(i0, ++i)); + return i; + } + else { + TypeData t = toPrimitiveTag(c); + if (t == null) + throw new BadBytecode("bad method descriptor: " + desc); + + types[n] = t; + return i + 1; + } + } + + private static TypeData toPrimitiveTag(char c) { + switch (c) { + case 'Z' : + case 'C' : + case 'B' : + case 'S' : + case 'I' : + return INTEGER; + case 'J' : + return LONG; + case 'F' : + return FLOAT; + case 'D' : + return DOUBLE; + case 'V' : + default : + return null; + } + } + + public static String getRetType(String desc) { + int i = desc.indexOf(')'); + if (i < 0) + return "java.lang.Object"; + + char c = desc.charAt(i + 1); + if (c == '[') + return desc.substring(i + 1); + else if (c == 'L') + return desc.substring(i + 2, desc.length() - 1).replace('/', '.'); + else + return "java.lang.Object"; + } } diff --git a/src/main/javassist/bytecode/stackmap/MapMaker.java b/src/main/javassist/bytecode/stackmap/MapMaker.java new file mode 100644 index 00000000..c7e92c04 --- /dev/null +++ b/src/main/javassist/bytecode/stackmap/MapMaker.java @@ -0,0 +1,433 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2006 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. + * + * 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.ClassPool; +import javassist.bytecode.*; + +/** + * Stack map maker. + */ +public class MapMaker extends Tracer { + private boolean moveon; + private BasicBlock[] blocks; + + public static void main(String[] args) throws Exception { + if (args.length > 1) { + main2(args); + return; + } + + ClassPool cp = ClassPool.getDefault(); + javassist.CtClass cc = cp.get(args[0]); + ClassFile cf = cc.getClassFile(); + java.util.List minfos = cf.getMethods(); + for (int i = 0; i < minfos.size(); i++) { + MethodInfo minfo = (MethodInfo)minfos.get(i); + CodeAttribute ca = minfo.getCodeAttribute(); + ca.setAttribute(MapMaker.getMap(cp, minfo)); + } + + cc.writeFile("tmp"); + } + + public static void main2(String[] args) throws Exception { + ClassPool cp = ClassPool.getDefault(); + javassist.CtClass cc = cp.get(args[0]); + MapMaker mm; + if (args[1].equals("_init_")) + mm = makeMapMaker(cp, cc.getDeclaredConstructors()[0].getMethodInfo()); + else + mm = makeMapMaker(cp, cc.getDeclaredMethod(args[1]).getMethodInfo()); + + if (mm == null) + System.out.println("single basic block"); + else { + BasicBlock[] blocks = mm.getBlocks(); + for (int i = 0; i < blocks.length; i++) + System.out.println(blocks[i]); + } + } + + /** + * Computes the stack map table of the given method and returns it. + * It returns null if the given method does not have to have a + * stack map table. + */ + public static StackMapTable getMap(ClassPool classes, MethodInfo minfo) + throws BadBytecode + { + MapMaker mm = makeMapMaker(classes, minfo); + if (mm == null) + return null; + else + return mm.toStackMap(); + } + + /* + * Makes basic blocks with stack maps. If the number of the basic blocks + * is one, this method returns null. + */ + public static MapMaker makeMapMaker(ClassPool classes, MethodInfo minfo) + throws BadBytecode + { + CodeAttribute ca = minfo.getCodeAttribute(); + CodeIterator ci = ca.iterator(); + ConstPool pool = minfo.getConstPool(); + ExceptionTable et = ca.getExceptionTable(); + BasicBlock[] blocks = BasicBlock.makeBlocks(ci, 0, ci.getCodeLength(), + et, 0, pool); + if (blocks.length < 2) + return null; + + boolean isStatic = (minfo.getAccessFlags() & AccessFlag.STATIC) != 0; + int maxStack = ca.getMaxStack(); + int maxLocals = ca.getMaxLocals(); + BasicBlock top = blocks[0]; + String desc = minfo.getDescriptor(); + top.initFirstBlock(maxStack, maxLocals, pool.getClassName(), desc, + isStatic, minfo.isConstructor()); + String retType = BasicBlock.getRetType(desc); + MapMaker mm = new MapMaker(classes, pool, maxStack, maxLocals, + blocks, retType, blocks[0]); + mm.make(ca.getCode(), et); + return mm; + } + + /** + * Constructs a tracer. + */ + MapMaker(ClassPool classes, ConstPool cp, + int maxStack, int maxLocals, BasicBlock[] bb, + String retType, BasicBlock init) { + this(classes, cp, maxStack, maxLocals, bb, retType); + TypeData[] srcTypes = init.localsTypes; + copyFrom(srcTypes.length, srcTypes, this.localsTypes); + } + + private MapMaker(ClassPool classes, ConstPool cp, + int maxStack, int maxLocals, BasicBlock[] bb, + String retType) + { + super(classes, cp, maxStack, maxLocals, retType); + blocks = bb; + } + + public BasicBlock[] getBlocks() { return blocks; } + + /** + * Runs an analyzer. + */ + void make(byte[] code, ExceptionTable et) throws BadBytecode { + make(code, blocks[0]); + traceExceptions(code, et); + int n = blocks.length; + for (int i = 0; i < n; i++) + evalExpected(blocks[i]); + } + + private void traceExceptions(byte[] code, ExceptionTable et) + throws BadBytecode + { + int n = et.size(); + for (int i = 0; i < n; i++) { + int startPc = et.startPc(i); + int handlerPc = et.handlerPc(i); + BasicBlock handler = BasicBlock.find(blocks, handlerPc); + if (handler.alreadySet()) + continue; + + BasicBlock thrower = BasicBlock.find(blocks, startPc); + TypeData[] srcTypes = thrower.localsTypes; + copyFrom(srcTypes.length, srcTypes, this.localsTypes); + int typeIndex = et.catchType(i); + String type; + if (typeIndex == 0) + type = "java.lang.Throwable"; + else + type = cpool.getClassInfo(typeIndex); + + stackTop = 1; + stackTypes[0] = new TypeData.ClassName(type); + recordStackMap(handler); + make(code, handler); + } + } + + // Phase 1: Code Tracing + + private void make(byte[] code, BasicBlock bb) + throws BadBytecode + { + int pos = bb.position; + int end = pos + bb.length; + moveon = true; + while (moveon && pos < end) + pos += doOpcode(pos, code); + + if (moveon && pos < code.length) { + this.copyFrom(this); + nextBlock(pos, code, 0); + } + } + + private void nextBlock(int pos, byte[] code, int offset) throws BadBytecode { + BasicBlock bb = BasicBlock.find(blocks, pos + offset); + if (bb.alreadySet()) { + mergeMap(stackTypes, bb.stackTypes); + mergeMap(localsTypes, bb.localsTypes); + } + else { + recordStackMap(bb); + MapMaker maker = new MapMaker(classPool, cpool, stackTypes.length, + localsTypes.length, blocks, returnType); + maker.copyFrom(this); + maker.make(code, bb); + } + } + + private static void mergeMap(TypeData[] srcTypes, TypeData[] destTypes) { + int n = srcTypes.length; + for (int i = 0; i < n; i++) { + TypeData s = srcTypes[i]; + TypeData d = destTypes[i]; + boolean sIsObj = false; + boolean dIsObj = false; + // s or b is null if it is TOP. + if (s != TOP && s.isObjectType()) + sIsObj = true; + + if (d != TOP && d.isObjectType()) + dIsObj = true; + + if (sIsObj && dIsObj) + d.merge(s); + else if (s != d) + destTypes[i] = TOP; + } + } + + private void copyFrom(MapMaker src) { + int sp = src.stackTop; + this.stackTop = sp; + copyFrom(sp, src.stackTypes, this.stackTypes); + TypeData[] srcTypes = src.localsTypes; + copyFrom(srcTypes.length, srcTypes, this.localsTypes); + } + + private 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 == null ? null : t.getSelf(); + if (t != TOP) + k = i; + } + + return k + 1; + } + + private void recordStackMap(BasicBlock target) + throws BadBytecode + { + int n = localsTypes.length; + TypeData[] tLocalsTypes = new TypeData[n]; + int k = copyFrom(n, localsTypes, tLocalsTypes); + + n = stackTypes.length; + TypeData[] tStackTypes = new TypeData[n]; + int st = stackTop; + copyFrom(st, stackTypes, tStackTypes); + + target.setStackMap(st, tStackTypes, k, tLocalsTypes); + } + + // Phase 2 + + void evalExpected(BasicBlock target) throws BadBytecode { + ClassPool cp = classPool; + evalExpected(cp, target.stackTop, target.stackTypes); + TypeData[] types = target.localsTypes; + evalExpected(cp, types.length, types); + } + + 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); + } + } + + // Phase 3 + + public StackMapTable toStackMap() { + BasicBlock[] blocks = this.blocks; + StackMapTable.Writer writer = new StackMapTable.Writer(32); + int n = blocks.length; + BasicBlock prev = blocks[0]; + int offsetDelta = prev.length; + for (int i = 1; i < n; i++) { + BasicBlock bb = blocks[i]; + if (bb.inbound > 0) { + bb.resetNumLocals(); + int diffL = stackMapDiff(prev.numLocals, prev.localsTypes, + bb.numLocals, bb.localsTypes); + toStackMapBody(writer, bb, diffL, offsetDelta); + offsetDelta = bb.length - 1; + prev = bb; + } + else + offsetDelta += bb.length; + + } + + return writer.toStackMapTable(cpool); + } + + private void toStackMapBody(StackMapTable.Writer writer, BasicBlock bb, + int diffL, int offsetDelta) { + // if diffL is -100, two TypeData arrays do not share + // any elements. + + int stackTop = bb.stackTop; + if (stackTop == 0) { + if (diffL == 0) { + writer.sameFrame(offsetDelta); + return; + } + else if (0 > diffL && diffL >= -3) { + writer.chopFrame(offsetDelta, -diffL); + return; + } + else if (0 < diffL && diffL <= 3) { + int[] tags = new int[diffL]; + int[] data = new int[diffL]; + fillStackMap(diffL, bb.numLocals - diffL, tags, data, + bb.localsTypes); + writer.appendFrame(offsetDelta, tags, data); + return; + } + } + else if (stackTop == 1 && diffL == 0) { + TypeData[] types = bb.stackTypes; + TypeData td = types[0]; + if (td == TOP) + writer.sameLocals(offsetDelta, StackMapTable.TOP, 0); + else + writer.sameLocals(offsetDelta, td.getTypeTag(), + td.getTypeData(cpool)); + return; + } + + int[] stags = new int[stackTop]; + int[] sdata = new int[stackTop]; + int nl = bb.numLocals; + int[] ltags = new int[nl]; + int[] ldata = new int[nl]; + fillStackMap(stackTop, 0, stags, sdata, bb.stackTypes); + fillStackMap(nl, 0, ltags, ldata, bb.localsTypes); + writer.fullFrame(offsetDelta, ltags, ldata, stags, sdata); + } + + private void fillStackMap(int num, int offset, int[] tags, int[] data, TypeData[] types) { + ConstPool cp = cpool; + for (int i = 0; i < num; i++) { + TypeData td = types[offset + i]; + if (td == TOP) { + tags[i] = StackMapTable.TOP; + data[i] = 0; + } + else { + tags[i] = td.getTypeTag(); + data[i] = td.getTypeData(cp); + } + } + } + + private static int stackMapDiff(int oldTdLen, TypeData[] oldTd, + int newTdLen, TypeData[] newTd) + { + int diff = newTdLen - oldTdLen; + int len; + if (diff > 0) + len = oldTdLen; + else + len = newTdLen; + + if (stackMapEq(oldTd, newTd, len)) + return diff; + else + return -100; + } + + private static boolean stackMapEq(TypeData[] oldTd, TypeData[] newTd, int len) { + for (int i = 0; i < len; i++) { + TypeData td = oldTd[i]; + if (td == TOP) { + if (newTd[i] != TOP) + return false; + } + else + if (!oldTd[i].equals(newTd[i])) + return false; + } + + return true; + } + + // Branch actions + + protected void visitBranch(int pos, byte[] code, int offset) throws BadBytecode { + nextBlock(pos, code, offset); + } + + protected void visitGoto(int pos, byte[] code, int offset) throws BadBytecode { + nextBlock(pos, code, offset); + moveon = false; + } + + protected void visitTableSwitch(int pos, byte[] code, int n, int offsetPos, int defaultOffset) + throws BadBytecode + { + nextBlock(pos, code, defaultOffset); + for (int i = 0; i < n; i++) { + nextBlock(pos, code, ByteArray.read32bit(code, offsetPos)); + offsetPos += 4; + } + + moveon = false; + } + + protected void visitLookupSwitch(int pos, byte[] code, int n, int pairsPos, int defaultOffset) + throws BadBytecode + { + nextBlock(pos, code, defaultOffset); + pairsPos += 4; + for (int i = 0; i < n; i++) { + nextBlock(pos, code, ByteArray.read32bit(code, pairsPos)); + pairsPos += 8; + } + + moveon = false; + } + + protected void visitReturn(int pos, byte[] code) { moveon = false; } + + protected void visitThrow(int pos, byte[] code) { moveon = false; } +} diff --git a/src/main/javassist/bytecode/stackmap/StackAnalyzer.java b/src/main/javassist/bytecode/stackmap/StackAnalyzer.java deleted file mode 100644 index 264ffe86..00000000 --- a/src/main/javassist/bytecode/stackmap/StackAnalyzer.java +++ /dev/null @@ -1,10 +0,0 @@ -package javassist.bytecode.stackmap; - -import javassist.bytecode.ConstPool; - -public class StackAnalyzer extends StackAnalyzerCore { - - public StackAnalyzer(ConstPool cp, int maxStack, int maxLocals) { - super(cp, maxStack, maxLocals); - } -} diff --git a/src/main/javassist/bytecode/stackmap/StackAnalyzerCore.java b/src/main/javassist/bytecode/stackmap/Tracer.java similarity index 64% rename from src/main/javassist/bytecode/stackmap/StackAnalyzerCore.java rename to src/main/javassist/bytecode/stackmap/Tracer.java index c3c79c86..16eabb80 100644 --- a/src/main/javassist/bytecode/stackmap/StackAnalyzerCore.java +++ b/src/main/javassist/bytecode/stackmap/Tracer.java @@ -1,93 +1,67 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2006 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. + * + * 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.StackMapTable; import javassist.bytecode.ByteArray; import javassist.bytecode.Opcode; import javassist.bytecode.ConstPool; import javassist.bytecode.Descriptor; import javassist.bytecode.BadBytecode; +import javassist.ClassPool; -public class StackAnalyzerCore { - private ConstPool cpool; - private int stackTop; - private int[] stackTypes; - private Object[] stackData; // String or Integer - - private int[] localsTypes; - private boolean[] localsUpdated; - private Object[] localsData; // String or Integer - - static final int EMPTY = -1; - static final int TOP = StackMapTable.TOP; - static final int INTEGER = StackMapTable.INTEGER; - static final int FLOAT = StackMapTable.FLOAT; - static final int DOUBLE = StackMapTable.DOUBLE; - static final int LONG = StackMapTable.LONG; - static final int NULL = StackMapTable.NULL; - static final int THIS = StackMapTable.THIS; - static final int OBJECT = StackMapTable.OBJECT; - static final int UNINIT = StackMapTable.UNINIT; - - public StackAnalyzerCore(ConstPool cp, int maxStack, int maxLocals) { - cpool = cp; - stackTop = 0; - stackTypes = null; - localsTypes = null; - growStack(maxStack); - growLocals(maxLocals); - } +/* + * A class for performing abstract interpretation. + * See also MapMaker class. + */ - public void clearUpdatedFlags() { - boolean[] updated = localsUpdated; - for (int i = 0; i < updated.length; i++) - updated[i] = false; - } +public abstract class Tracer implements TypeTag { + protected ClassPool classPool; + protected ConstPool cpool; + protected String returnType; - public void growStack(int size) { - int oldSize; - int[] types = new int[size]; - Object[] data = new Object[size]; - if (stackTypes == null) - oldSize = 0; - else { - oldSize = stackTypes.length; - System.arraycopy(stackTypes, 0, types, 0, oldSize); - System.arraycopy(stackData, 0, data, 0, oldSize); - } + protected int stackTop; + protected TypeData[] stackTypes; + protected TypeData[] localsTypes; - stackTypes = types; - stackData = data; - for (int i = oldSize; i < size; i++) - stackTypes[i] = EMPTY; + public Tracer(ClassPool classes, ConstPool cp, int maxStack, int maxLocals, + String retType) { + classPool = classes; + cpool = cp; + returnType = retType; + stackTop = 0; + stackTypes = new TypeData[maxStack]; + localsTypes = new TypeData[maxLocals]; } - public void growLocals(int size) { - int oldSize; - int[] types = new int[size]; - boolean[] updated = new boolean[size]; - Object[] data = new Object[size]; - if (localsTypes == null) - oldSize = 0; - else { - oldSize = localsTypes.length; - System.arraycopy(localsTypes, 0, types, 0, oldSize); - System.arraycopy(localsUpdated, 0, updated, 0, oldSize); - System.arraycopy(localsData, 0, data, 0, oldSize); - } - - localsTypes = types; - localsUpdated = updated; - localsData = data; - for (int i = oldSize; i < size; i++) - localsTypes[i] = EMPTY; - } + /* If the type is LONG or DOUBLE, + * the next local variable is also read. + * IINC (or WIDE IINC) calls only readLocal() but it does not call writeLocal(). + */ + private void readLocal(int reg) {} - public void pushType(int type, Object data) { - stackTypes[stackTop] = type; - stackData[stackTop++] = data; - } + private void writeLocal(int reg) {} /** + * Does abstract interpretation on the given bytecode instruction. + * It records whether or not a local variable (i.e. register) is accessed. + * If the instruction requires that a local variable or + * a stack element has a more specific type, this method updates the + * type of it. + * + * @pos the position of the instruction. * @return the size of the instruction at POS. */ protected int doOpcode(int pos, byte[] code) throws BadBytecode { @@ -104,12 +78,30 @@ public class StackAnalyzerCore { return doOpcode148_201(pos, code, op); } - protected void visitBranch(int pos, byte[] code, int offset) {} - protected void visitGoto(int pos, byte[] code, int offset) {} - protected void visitTableSwitch(int pos, byte[] code, int n, int offsetPos, int defaultByte) {} - protected void visitLookupSwitch(int pos, byte[] code, int n, int pairsPos, int defaultByte) {} - protected void visitReturn(int pos, byte[] code) {} - protected void visitThrow(int pos, byte[] code) {} + protected void visitBranch(int pos, byte[] code, int offset) throws BadBytecode {} + protected void visitGoto(int pos, byte[] code, int offset) throws BadBytecode {} + protected void visitReturn(int pos, byte[] code) throws BadBytecode {} + protected void visitThrow(int pos, byte[] code) throws BadBytecode {} + + /** + * @param pos the position of TABLESWITCH + * @param code bytecode + * @param n the number of case labels + * @param offsetPos the position of the branch-target table. + * @param defaultOffset the offset to the default branch target. + */ + protected void visitTableSwitch(int pos, byte[] code, int n, + int offsetPos, int defaultOffset) throws BadBytecode {} + + /** + * @param pos the position of LOOKUPSWITCH + * @param code bytecode + * @param n the number of case labels + * @param offsetPos the position of the table of pairs of a value and a branch target. + * @param defaultOffset the offset to the default branch target. + */ + protected void visitLookupSwitch(int pos, byte[] code, int n, + int pairsPos, int defaultOffset) throws BadBytecode {} /** * Invoked when the visited instruction is jsr. @@ -119,7 +111,7 @@ public class StackAnalyzerCore { } /** - * Invoked when the visited instruction is ret. + * Invoked when the visited instruction is ret or wide ret. */ protected void visitRET(int pos, byte[] code) throws BadBytecode { throwBadBytecode(pos, "ret"); @@ -130,12 +122,13 @@ public class StackAnalyzerCore { } private int doOpcode0_53(int pos, byte[] code, int op) throws BadBytecode { - int[] stackTypes = this.stackTypes; + int reg; + TypeData[] stackTypes = this.stackTypes; switch (op) { case Opcode.NOP : break; case Opcode.ACONST_NULL : - stackTypes[stackTop++] = NULL; + stackTypes[stackTop++] = new TypeData.NullType(); break; case Opcode.ICONST_M1 : case Opcode.ICONST_0 : @@ -173,22 +166,21 @@ public class StackAnalyzerCore { doLDC(ByteArray.readU16bit(code, pos + 1)); return 3; case Opcode.ILOAD : - return doXLOAD(INTEGER); + return doXLOAD(INTEGER, code, pos); case Opcode.LLOAD : - return doXLOAD(LONG); + return doXLOAD(LONG, code, pos); case Opcode.FLOAD : - return doXLOAD(FLOAT); + return doXLOAD(FLOAT, code, pos); case Opcode.DLOAD : - return doXLOAD(DOUBLE); + return doXLOAD(DOUBLE, code, pos); case Opcode.ALOAD : - stackTypes[stackTop] = OBJECT; - stackData[stackTop++] = localsData[code[pos + 1] & 0xff]; - return 2; + return doALOAD(code[pos + 1] & 0xff); case Opcode.ILOAD_0 : case Opcode.ILOAD_1 : case Opcode.ILOAD_2 : case Opcode.ILOAD_3 : stackTypes[stackTop++] = INTEGER; + readLocal(op - Opcode.ILOAD_0); break; case Opcode.LLOAD_0 : case Opcode.LLOAD_1 : @@ -196,12 +188,14 @@ public class StackAnalyzerCore { case Opcode.LLOAD_3 : stackTypes[stackTop++] = LONG; stackTypes[stackTop++] = TOP; + readLocal(op - Opcode.LLOAD_0); break; case Opcode.FLOAD_0 : case Opcode.FLOAD_1 : case Opcode.FLOAD_2 : case Opcode.FLOAD_3 : stackTypes[stackTop++] = FLOAT; + readLocal(op - Opcode.FLOAD_0); break; case Opcode.DLOAD_0 : case Opcode.DLOAD_1 : @@ -209,13 +203,15 @@ public class StackAnalyzerCore { case Opcode.DLOAD_3 : stackTypes[stackTop++] = DOUBLE; stackTypes[stackTop++] = TOP; + readLocal(op - Opcode.DLOAD_0); break; case Opcode.ALOAD_0 : case Opcode.ALOAD_1 : case Opcode.ALOAD_2 : case Opcode.ALOAD_3 : - stackTypes[stackTop] = OBJECT; - stackData[stackTop++] = localsData[op - Opcode.ALOAD_0]; + reg = op - Opcode.ALOAD_0; + stackTypes[stackTop++] = localsTypes[reg]; + readLocal(reg); break; case Opcode.IALOAD : stackTypes[--stackTop - 1] = INTEGER; @@ -233,12 +229,11 @@ public class StackAnalyzerCore { break; case Opcode.AALOAD : { int s = --stackTop - 1; - stackTypes[s] = OBJECT; - Object data = stackData[s]; - if (data != null && data instanceof String) - stackData[s] = getDerefType((String)data); - else + TypeData data = stackTypes[s]; + if (data == null || data.isBasicType()) throw new BadBytecode("bad AALOAD"); + else + stackTypes[s] = new TypeData.ArrayElement(data); break; } case Opcode.BALOAD : @@ -254,12 +249,10 @@ public class StackAnalyzerCore { } private void doLDC(int index) { - int[] stackTypes = this.stackTypes; + TypeData[] stackTypes = this.stackTypes; int tag = cpool.getTag(index); - if (tag == ConstPool.CONST_String) { - stackTypes[stackTop] = OBJECT; - stackData[stackTop++] = "java/lang/String"; - } + if (tag == ConstPool.CONST_String) + stackTypes[stackTop++] = new TypeData.ClassName("java.lang.String"); else if (tag == ConstPool.CONST_Integer) stackTypes[stackTop++] = INTEGER; else if (tag == ConstPool.CONST_Float) @@ -272,43 +265,35 @@ public class StackAnalyzerCore { stackTypes[stackTop++] = DOUBLE; stackTypes[stackTop++] = TOP; } - else if (tag == ConstPool.CONST_Class) { - stackTypes[stackTop] = OBJECT; - stackData[stackTop++] = "java/lang/Class"; - } + else if (tag == ConstPool.CONST_Class) + stackTypes[stackTop++] = new TypeData.ClassName("java.lang.Class"); else throw new RuntimeException("bad LDC: " + tag); } - private int doXLOAD(int type) { - int[] stackTypes = this.stackTypes; + private int doXLOAD(TypeData type, byte[] code, int pos) { + int localVar = code[pos + 1] & 0xff; + return doXLOAD(localVar, type); + } + + private int doXLOAD(int localVar, TypeData type) { stackTypes[stackTop++] = type; if (type == LONG || type == DOUBLE) stackTypes[stackTop++] = TOP; + readLocal(localVar); return 2; } - private String getDerefType(String type) throws BadBytecode { - if (type.charAt(0) == '[') { - String type2 = type.substring(1); - if (type2.length() > 0) { - char c = type2.charAt(0); - if (c == '[') - return type2; - else if (c == 'L') - return type2.substring(1, type.length() - 2); - else - return type2; - } - } - - throw new BadBytecode("bad array type for AALOAD: " + type); + private int doALOAD(int localVar) { // int localVar, TypeData type) { + stackTypes[stackTop++] = localsTypes[localVar]; + readLocal(localVar); + return 2; } private int doOpcode54_95(int pos, byte[] code, int op) { - int[] localsTypes = this.localsTypes; - int[] stackTypes = this.stackTypes; + TypeData[] localsTypes = this.localsTypes; + TypeData[] stackTypes = this.stackTypes; switch (op) { case Opcode.ISTORE : return doXSTORE(pos, code, INTEGER); @@ -319,15 +304,15 @@ public class StackAnalyzerCore { case Opcode.DSTORE : return doXSTORE(pos, code, DOUBLE); case Opcode.ASTORE : - return doXSTORE(pos, code, OBJECT); + return doASTORE(code[pos + 1] & 0xff); case Opcode.ISTORE_0 : case Opcode.ISTORE_1 : case Opcode.ISTORE_2 : case Opcode.ISTORE_3 : { int var = op - Opcode.ISTORE_0; localsTypes[var] = INTEGER; - localsUpdated[var] = true; } - stackTop--; + writeLocal(var); + stackTop--; } break; case Opcode.LSTORE_0 : case Opcode.LSTORE_1 : @@ -336,8 +321,8 @@ public class StackAnalyzerCore { { int var = op - Opcode.LSTORE_0; localsTypes[var] = LONG; localsTypes[var + 1] = TOP; - localsUpdated[var] = true; } - stackTop -= 2; + writeLocal(var); + stackTop -= 2; } break; case Opcode.FSTORE_0 : case Opcode.FSTORE_1 : @@ -345,8 +330,8 @@ public class StackAnalyzerCore { case Opcode.FSTORE_3 : { int var = op - Opcode.FSTORE_0; localsTypes[var] = FLOAT; - localsUpdated[var] = true; } - stackTop--; + writeLocal(var); + stackTop--; } break; case Opcode.DSTORE_0 : case Opcode.DSTORE_1 : @@ -355,18 +340,16 @@ public class StackAnalyzerCore { { int var = op - Opcode.DSTORE_0; localsTypes[var] = DOUBLE; localsTypes[var + 1] = TOP; - localsUpdated[var] = true; } - stackTop -= 2; + writeLocal(var); + stackTop -= 2; } break; case Opcode.ASTORE_0 : case Opcode.ASTORE_1 : case Opcode.ASTORE_2 : case Opcode.ASTORE_3 : { int var = op - Opcode.ASTORE_0; - localsTypes[var] = OBJECT; - localsUpdated[var] = true; - localsData[var] = stackData[--stackTop]; } - break; + doASTORE(var); + break; } case Opcode.IASTORE : case Opcode.LASTORE : case Opcode.FASTORE : @@ -386,7 +369,6 @@ public class StackAnalyzerCore { case Opcode.DUP : { int sp = stackTop; stackTypes[sp] = stackTypes[sp - 1]; - stackData[sp] = stackData[sp - 1]; stackTop = sp + 1; break; } case Opcode.DUP_X1 : @@ -395,7 +377,6 @@ public class StackAnalyzerCore { doDUP_XX(1, len); int sp = stackTop; stackTypes[sp - len] = stackTypes[sp]; - stackData[sp - len] = stackData[sp]; stackTop = sp + 1; break; } case Opcode.DUP2 : @@ -406,23 +387,16 @@ public class StackAnalyzerCore { case Opcode.DUP2_X2 : { int len = op - Opcode.DUP2_X1 + 3; doDUP_XX(2, len); - Object[] stackData = this.stackData; int sp = stackTop; stackTypes[sp - len] = stackTypes[sp]; - stackData[sp - len] = stackData[sp]; stackTypes[sp - len + 1] = stackTypes[sp + 1]; - stackData[sp - len + 1] = stackData[sp + 1]; stackTop = sp + 2; break; } case Opcode.SWAP : { - Object[] stackData = this.stackData; int sp = stackTop - 1; - int t = stackTypes[sp]; - Object d = stackData[sp]; + TypeData t = stackTypes[sp]; stackTypes[sp] = stackTypes[sp - 1]; - stackData[sp] = stackData[sp - 1]; stackTypes[sp - 1] = t; - stackData[sp - 1] = d; break; } default : throw new RuntimeException("fatal"); @@ -431,45 +405,52 @@ public class StackAnalyzerCore { return 1; } - private int doXSTORE(int pos, byte[] code, int type) { + private int doXSTORE(int pos, byte[] code, TypeData type) { int index = code[pos + 1] & 0xff; return doXSTORE(index, type); } - private int doXSTORE(int index, int type) { + private int doXSTORE(int index, TypeData type) { stackTop--; + writeLocal(index); localsTypes[index] = type; - localsUpdated[index] = true; if (type == LONG || type == DOUBLE) { stackTop--; localsTypes[index + 1] = TOP; } - else if (type == OBJECT) - localsData[index] = stackData[stackTop]; + + return 2; + } + + private int doASTORE(int index) { + stackTop--; + writeLocal(index); + // implicit upcast might be done. + localsTypes[index] = stackTypes[stackTop].copy(); return 2; } private void doDUP_XX(int delta, int len) { - int types[] = stackTypes; - Object data[] = stackData; + TypeData types[] = stackTypes; int sp = stackTop; int end = sp - len; while (sp > end) { types[sp + delta] = types[sp]; - data[sp + delta] = data[sp]; sp--; } } private int doOpcode96_147(int pos, byte[] code, int op) { if (op <= Opcode.LXOR) { // IADD...LXOR - stackTop -= Opcode.STACK_GROW[op]; + stackTop += Opcode.STACK_GROW[op]; return 1; } switch (op) { case Opcode.IINC : + readLocal(code[pos + 1] & 0xff); + // this does not call writeLocal(). return 3; case Opcode.I2L : stackTypes[stackTop] = LONG; @@ -600,56 +581,56 @@ public class StackAnalyzerCore { visitReturn(pos, code); break; case Opcode.ARETURN : - stackTop--; + TypeData.setType(stackTypes[--stackTop], returnType, classPool); visitReturn(pos, code); break; case Opcode.RETURN : visitReturn(pos, code); break; case Opcode.GETSTATIC : - return doFieldAccess(pos, code, true); + return doGetField(pos, code, false); case Opcode.PUTSTATIC : - return doFieldAccess(pos, code, false); + return doPutField(pos, code, false); case Opcode.GETFIELD : - stackTop--; - return doFieldAccess(pos, code, true); + return doGetField(pos, code, true); case Opcode.PUTFIELD : - stackTop--; - return doFieldAccess(pos, code, false); + return doPutField(pos, code, true); case Opcode.INVOKEVIRTUAL : case Opcode.INVOKESPECIAL : - return doInvokeMethod(pos, code, 1); + return doInvokeMethod(pos, code, true); case Opcode.INVOKESTATIC : - return doInvokeMethod(pos, code, 0); + return doInvokeMethod(pos, code, false); case Opcode.INVOKEINTERFACE : - return doInvokeIntfMethod(pos, code, 1); + return doInvokeIntfMethod(pos, code); case 186 : throw new RuntimeException("bad opcode 186"); case Opcode.NEW : { int i = ByteArray.readU16bit(code, pos + 1); - stackTypes[stackTop - 1] = UNINIT; - stackData[stackTop - 1] = new Integer(pos); + stackTypes[stackTop++] + = new TypeData.UninitData(pos, cpool.getClassInfo(i)); return 3; } case Opcode.NEWARRAY : return doNEWARRAY(pos, code); case Opcode.ANEWARRAY : { int i = ByteArray.readU16bit(code, pos + 1); - stackTypes[stackTop - 1] = OBJECT; - stackData[stackTop - 1] - = "[L" + cpool.getClassInfo(i).replace('.', '/') + ";"; + String type = cpool.getClassInfo(i).replace('.', '/'); + stackTypes[stackTop - 1] + = new TypeData.ClassName("[L" + type + ";"); return 3; } case Opcode.ARRAYLENGTH : stackTypes[stackTop - 1] = INTEGER; break; case Opcode.ATHROW : - stackTop--; // branch? + TypeData.setType(stackTypes[--stackTop], "java.lang.Throwable", classPool); visitThrow(pos, code); break; case Opcode.CHECKCAST : { + // TypeData.setType(stackData[stackTop - 1], "java.lang.Object", classPool); int i = ByteArray.readU16bit(code, pos + 1); - stackData[stackTop - 1] = cpool.getClassInfo(i); + stackTypes[stackTop - 1] = new TypeData.ClassName(cpool.getClassInfo(i)); return 3; } case Opcode.INSTANCEOF : + // TypeData.setType(stackData[stackTop - 1], "java.lang.Object", classPool); stackTypes[stackTop - 1] = INTEGER; return 3; case Opcode.MONITORENTER : @@ -676,25 +657,25 @@ public class StackAnalyzerCore { return 1; } - private int doWIDE(int pos, byte[] code) { + private int doWIDE(int pos, byte[] code) throws BadBytecode { int op = code[pos + 1] & 0xff; switch (op) { case Opcode.ILOAD : - doXLOAD(INTEGER); + doWIDE_XLOAD(pos, code, INTEGER); break; case Opcode.LLOAD : - doXLOAD(LONG); + doWIDE_XLOAD(pos, code, LONG); break; case Opcode.FLOAD : - doXLOAD(FLOAT); + doWIDE_XLOAD(pos, code, FLOAT); break; case Opcode.DLOAD : - doXLOAD(DOUBLE); - break; - case Opcode.ALOAD : - stackTypes[stackTop] = OBJECT; - stackData[stackTop++] = localsData[ByteArray.readU16bit(code, pos)]; + doWIDE_XLOAD(pos, code, DOUBLE); break; + case Opcode.ALOAD : { + int index = ByteArray.readU16bit(code, pos + 2); + doALOAD(index); + break; } case Opcode.ISTORE : return doWIDE_STORE(pos, code, INTEGER); case Opcode.LSTORE : @@ -703,11 +684,15 @@ public class StackAnalyzerCore { return doWIDE_STORE(pos, code, FLOAT); case Opcode.DSTORE : return doWIDE_STORE(pos, code, DOUBLE); - case Opcode.ASTORE : - return doWIDE_STORE(pos, code, OBJECT); + case Opcode.ASTORE : { + int index = ByteArray.readU16bit(code, pos + 2); + return doASTORE(index); } case Opcode.IINC : + readLocal(ByteArray.readU16bit(code, pos + 2)); + // this does not call writeLocal(). return 6; case Opcode.RET : + visitRET(pos, code); break; default : throw new RuntimeException("bad WIDE instruction: " + op); @@ -716,25 +701,47 @@ public class StackAnalyzerCore { return 4; } - private int doWIDE_STORE(int pos, byte[] code, int type) { - int index = ByteArray.readU16bit(code, pos); + private int doWIDE_XLOAD(int pos, byte[] code, TypeData type) { + int index = ByteArray.readU16bit(code, pos + 2); + return doXLOAD(index, type); + } + + private int doWIDE_STORE(int pos, byte[] code, TypeData type) { + int index = ByteArray.readU16bit(code, pos + 2); return doXSTORE(index, type); } - private int doFieldAccess(int pos, byte[] code, boolean isGet) { + private int doPutField(int pos, byte[] code, boolean notStatic) throws BadBytecode { int index = ByteArray.readU16bit(code, pos + 1); String desc = cpool.getFieldrefType(index); - if (isGet) - pushMemberType(desc); - else - stackTop -= Descriptor.dataSize(desc); + stackTop -= Descriptor.dataSize(desc); + char c = desc.charAt(0); + if (c == 'L') + TypeData.setType(stackTypes[stackTop], getFieldClassName(desc, 0), classPool); + else if (c == '[') + TypeData.setType(stackTypes[stackTop], desc, classPool); + + setFieldTarget(notStatic, index); + return 3; + } + private int doGetField(int pos, byte[] code, boolean notStatic) throws BadBytecode { + int index = ByteArray.readU16bit(code, pos + 1); + setFieldTarget(notStatic, index); + String desc = cpool.getFieldrefType(index); + pushMemberType(desc); return 3; } + private void setFieldTarget(boolean notStatic, int index) throws BadBytecode { + if (notStatic) { + String className = cpool.getFieldrefClassName(index); + TypeData.setType(stackTypes[--stackTop], className, classPool); + } + } + private int doNEWARRAY(int pos, byte[] code) { int s = stackTop - 1; - stackTypes[s] = OBJECT; String type; switch (code[pos + 1] & 0xff) { case Opcode.T_BOOLEAN : @@ -765,7 +772,7 @@ public class StackAnalyzerCore { throw new RuntimeException("bad newarray"); } - stackData[s] = type; + stackTypes[s] = new TypeData.ClassName(type); return 2; } @@ -773,29 +780,31 @@ public class StackAnalyzerCore { int i = ByteArray.readU16bit(code, pos + 1); int dim = code[pos + 3] & 0xff; stackTop -= dim - 1; - String type = cpool.getClassInfo(i); - StringBuffer sbuf = new StringBuffer(); - while (dim-- > 0) - sbuf.append('['); - - sbuf.append('L').append(type.replace('.', '/')).append(';'); - stackTypes[stackTop - 1] = OBJECT; - stackData[stackTop - 1] = sbuf.toString(); + + String type = cpool.getClassInfo(i).replace('.', '/'); + stackTypes[stackTop - 1] = new TypeData.ClassName(type); return 4; } - private int doInvokeMethod(int pos, byte[] code, int targetSize) { + private int doInvokeMethod(int pos, byte[] code, boolean notStatic) throws BadBytecode { int i = ByteArray.readU16bit(code, pos + 1); String desc = cpool.getMethodrefType(i); - stackTop -= Descriptor.paramSize(desc) + targetSize; + checkParamTypes(desc, 1); + if (notStatic) { + String className = cpool.getMethodrefClassName(i); + TypeData.setType(stackTypes[--stackTop], className, classPool); + } + pushMemberType(desc); return 3; } - private int doInvokeIntfMethod(int pos, byte[] code, int targetSize) { + private int doInvokeIntfMethod(int pos, byte[] code) throws BadBytecode { int i = ByteArray.readU16bit(code, pos + 1); String desc = cpool.getInterfaceMethodrefType(i); - stackTop -= Descriptor.paramSize(desc) + targetSize; + checkParamTypes(desc, 1); + String className = cpool.getInterfaceMethodrefClassName(i); + TypeData.setType(stackTypes[--stackTop], className, classPool); pushMemberType(desc); return 5; } @@ -809,17 +818,14 @@ public class StackAnalyzerCore { + descriptor); } - int[] types = stackTypes; + TypeData[] types = stackTypes; int index = stackTop; switch (descriptor.charAt(top)) { case '[' : - types[index] = OBJECT; - stackData[index] = descriptor.substring(top); + types[index] = new TypeData.ClassName(descriptor.substring(top)); break; case 'L' : - types[index] = OBJECT; - stackData[index] = descriptor.substring(top + 1, - descriptor.indexOf(';', top)); + types[index] = new TypeData.ClassName(getFieldClassName(descriptor, top)); break; case 'J' : types[index] = LONG; @@ -843,4 +849,42 @@ public class StackAnalyzerCore { stackTop++; } + + private static String getFieldClassName(String desc, int index) { + return desc.substring(index + 1, desc.length() - 1).replace('/', '.'); + } + + private void checkParamTypes(String desc, int i) throws BadBytecode { + char c = desc.charAt(i); + if (c == ')') + return; + + int k = i; + boolean array = false; + while (c == '[') { + array = true; + c = desc.charAt(++k); + } + + if (c == 'L') { + k = desc.indexOf(';', k) + 1; + if (k <= 0) + throw new IndexOutOfBoundsException("bad descriptor"); + } + else + k++; + + checkParamTypes(desc, k); + if (!array && (c == 'J' || c == 'D')) + stackTop -= 2; + else + stackTop--; + + if (array) + TypeData.setType(stackTypes[stackTop], + desc.substring(i, k), classPool); + else if (c == 'L') + TypeData.setType(stackTypes[stackTop], + 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 new file mode 100644 index 00000000..dbe95c99 --- /dev/null +++ b/src/main/javassist/bytecode/stackmap/TypeData.java @@ -0,0 +1,467 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2006 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. + * + * 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.ClassPool; +import javassist.CtClass; +import javassist.NotFoundException; +import javassist.bytecode.ConstPool; +import javassist.bytecode.StackMapTable; +import javassist.bytecode.BadBytecode; +import java.util.ArrayList; + +public abstract class TypeData { + /* Memo: + * array type is a subtype of Cloneable and Serializable + */ + + protected ArrayList equivalences; + + protected TypeData() { + equivalences = new ArrayList(); + equivalences.add(this); + } + + public void merge(TypeData neighbor) { + if (this == neighbor) + return; + + ArrayList list = equivalences; + ArrayList list2 = neighbor.equivalences; + if (list == list2) + return; + + int n = list2.size(); + for (int i = 0; i < n; i++) { + TypeData td = (TypeData)list2.get(i); + add(list, td); + td.equivalences = list; + } + } + + 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); + } + + /** + * Sets the type name of this object type. If the given type name is + * a subclass of the current type name, then the given name becomes + * the name of this object type. + * + * @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 == null) + throw new BadBytecode("unset variable"); + else + 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; } + + /* An operand value is copied when it is stored in a + * local variable. + */ + public abstract TypeData copy(); + + public boolean isBasicType() { return false; } + public boolean isObjectType() { return false; } + 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; + protected abstract boolean hasExpectedType(); // only TypeName can return true. + public abstract String getExpected() throws BadBytecode; + + /** + * Primitive types. + */ + protected static class BasicType extends TypeData { + private String name; + private int typeTag; + + public BasicType(String type, int tag) { + name = type; + typeTag = tag; + } + + public boolean equals(Object obj) { + return this == obj; + } + + public int getTypeTag() { return typeTag; } + public int getTypeData(ConstPool cp) { return 0; } + + public boolean isBasicType() { return true; } + + public TypeData copy() { + return this; + } + + public void evalExpectedType(ClassPool cp) throws BadBytecode {} + + public String getExpected() throws BadBytecode { + return name; + } + + public String getName() { + return name; + } + + protected boolean hasExpectedType() { + return false; + } + + protected 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 { + private String expectedName; + private CtClass cache; + private boolean evalDone; + + protected TypeName() { + expectedName = null; + cache = null; + evalDone = false; + } + + /* 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); + } + + /* NullType overrides this method. + */ + protected int getTypeData2(ConstPool cp, String type) { + return cp.addClassInfo(type); + } + + 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 isObjectType() { return true; } + + protected void setType(String typeName, ClassPool cp) throws BadBytecode { + if (update(cp, expectedName, typeName)) + expectedName = typeName; + } + + public void evalExpectedType(ClassPool cp) throws BadBytecode { + if (this.evalDone) + return; + + ArrayList equiv = this.equivalences; + String name = this.expectedName; + int n = equiv.size(); + for (int i = 0; i < n; i++) { + TypeData td = (TypeData)equiv.get(i); + if (td.hasExpectedType()) { + TypeName tn = (TypeName)td; + if (update(cp, name, tn.expectedName)) + name = tn.expectedName; + } + } + + if (name == null) + name = evalExpectedType2(equivalences, n); + + for (int i = 0; i < n; i++) { + TypeData td = (TypeData)equiv.get(i); + if (td.hasExpectedType()) { + TypeName tn = (TypeName)td; + tn.expectedName = name; + tn.cache = null; + tn.evalDone = true; + } + } + } + + 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; + } + + return origName; + } + + protected boolean hasExpectedType() { return true; } + + 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; + + try { + if (cache == null) + cache = cp.get(oldName); + + CtClass cache2 = cp.get(typeName); + if (cache2.subclassOf(cache)) { + cache = cache2; + return true; + } + else + return false; + } + catch (NotFoundException e) { + throw new BadBytecode("cannot find " + e.getMessage()); + } + } + + 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; + } + } + + public String toString() { + try { + String en = expectedName; + if (en != null) + return en; + + String name = getName(); + if (equivalences.size() == 1) + return name; + else + return name + "?"; + } + catch (BadBytecode e) { + return "<" + e.getMessage() + ">"; + } + } + } + + /** + * Type data for OBJECT. + */ + 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 TypeData copy() { + return new ClassName(name); + } + + public String getName() { // never returns null. + return name; + } + } + + /** + * 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 name + } + + public TypeData copy() { + return new NullType(); + } + + public boolean isNullType() { return true; } + + public int getTypeTag() { + try { + if ("null".equals(getExpected())) + return StackMapTable.NULL; + else + return super.getTypeTag(); + } + catch (BadBytecode e) { + throw new RuntimeException("fatal error: ", e); + } + } + + protected int getTypeData2(ConstPool cp, String type) { + if ("null".equals(type)) + return 0; + else + return super.getTypeData2(cp, type); + } + } + + /** + * 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; + + public ArrayElement(TypeData a) { // a is never null + array = a; + } + + public TypeData copy() { + return new ArrayElement(array); + } + + protected void setType(String typeName, ClassPool cp) throws BadBytecode { + super.setType(typeName, cp); + array.setType(getArrayType(typeName), cp); + } + + public String getName() throws BadBytecode { + String name = array.getName(); + if (name.length() > 1 && name.charAt(0) == '[') { + char c = name.charAt(1); + if (c == 'L') + return name.substring(2, name.length() - 1).replace('/', '.'); + else if (c == '[') + return name.substring(1); + } + + throw new BadBytecode("bad array type for AALOAD: " + + name); + } + + public static String getArrayType(String elementType) { + if (elementType.charAt(0) == '[') + return "[" + elementType; + else + return "[L" + elementType.replace('.', '/') + ";"; + } + } + + /** + * Type data for UNINIT. + */ + public static class UninitData extends TypeData { + String className; + int offset; + boolean initialized; + + UninitData(int offset, String className) { + this.className = className; + this.offset = offset; + this.initialized = false; + } + + 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 TypeData getSelf() { + if (initialized) + return copy(); + else + return this; + } + + public TypeData copy() { + return new ClassName(className); + } + + protected void setType(String typeName, ClassPool cp) throws BadBytecode { + initialized = true; + } + + public void evalExpectedType(ClassPool cp) throws BadBytecode {} + + protected boolean hasExpectedType() { return false; } + + public String getName() { + return className; + } + + public String getExpected() { return className; } + + public String toString() { return "uninit:" + className + "@" + offset; } + } + + public static class UninitThis extends UninitData { + UninitThis(String className) { + super(-1, className); + } + + public int getTypeTag() { return StackMapTable.THIS; } + public int getTypeData(ConstPool cp) { return 0; } + + public boolean equals(Object obj) { + return obj instanceof UninitThis; + } + + public String toString() { return "uninit:this"; } + } +} diff --git a/src/main/javassist/bytecode/stackmap/TypeTag.java b/src/main/javassist/bytecode/stackmap/TypeTag.java new file mode 100644 index 00000000..85dd2b1b --- /dev/null +++ b/src/main/javassist/bytecode/stackmap/TypeTag.java @@ -0,0 +1,28 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2006 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. + * + * 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.StackMapTable; + +public interface TypeTag { + TypeData TOP = null; + TypeData INTEGER = new TypeData.BasicType("int", StackMapTable.INTEGER); + TypeData FLOAT = new TypeData.BasicType("float", StackMapTable.FLOAT); + TypeData DOUBLE = new TypeData.BasicType("double", StackMapTable.DOUBLE); + TypeData LONG = new TypeData.BasicType("long", StackMapTable.LONG); + + // and NULL, THIS, OBJECT, UNINIT +} -- 2.39.5