From 5a4853ee87743b72a5a0c1486f3af4ce04ae8774 Mon Sep 17 00:00:00 2001 From: chiba Date: Thu, 4 Oct 2012 07:37:39 +0000 Subject: [PATCH] fixed JASSIST-174 git-svn-id: http://anonsvn.jboss.org/repos/javassist/trunk@669 30ef5769-5b8d-40dd-aea6-55b5d6557bb3 --- src/main/javassist/CtBehavior.java | 5 + .../javassist/bytecode/AttributeInfo.java | 2 + .../bytecode/BootstrapMethodsAttribute.java | 123 ++++++ src/main/javassist/bytecode/Bytecode.java | 21 +- src/main/javassist/bytecode/CodeAnalyzer.java | 4 + .../javassist/bytecode/CodeAttribute.java | 7 + src/main/javassist/bytecode/CodeIterator.java | 19 +- src/main/javassist/bytecode/ConstPool.java | 356 ++++++++++++++++++ .../bytecode/InstructionPrinter.java | 4 +- src/main/javassist/bytecode/MethodInfo.java | 6 + src/main/javassist/bytecode/Mnemonic.java | 5 +- src/main/javassist/bytecode/Opcode.java | 3 +- .../javassist/bytecode/StackMapTable.java | 4 + .../javassist/bytecode/analysis/Executor.java | 19 +- .../javassist/bytecode/stackmap/Tracer.java | 19 +- src/test/javassist/JvstTest4.java | 11 +- src/test/javassist/bytecode/BytecodeTest.java | 46 +++ src/test/javassist/bytecode/StackMapTest.java | 165 ++++++++ src/test/test4/InvokeDyn.java | 14 + 19 files changed, 816 insertions(+), 17 deletions(-) create mode 100644 src/main/javassist/bytecode/BootstrapMethodsAttribute.java create mode 100644 src/test/test4/InvokeDyn.java diff --git a/src/main/javassist/CtBehavior.java b/src/main/javassist/CtBehavior.java index 06873e2c..1ca8408b 100644 --- a/src/main/javassist/CtBehavior.java +++ b/src/main/javassist/CtBehavior.java @@ -26,6 +26,11 @@ import javassist.expr.ExprEditor; * or a static constructor (class initializer). * It is the abstract super class of * CtMethod and CtConstructor. + * + *

To directly read or modify bytecode, obtain MethodInfo + * objects. + * + * @see #getMethodInfo() */ public abstract class CtBehavior extends CtMember { protected MethodInfo methodInfo; diff --git a/src/main/javassist/bytecode/AttributeInfo.java b/src/main/javassist/bytecode/AttributeInfo.java index a6cbc09b..2cf878eb 100644 --- a/src/main/javassist/bytecode/AttributeInfo.java +++ b/src/main/javassist/bytecode/AttributeInfo.java @@ -77,6 +77,8 @@ public class AttributeInfo { if (nameStr.charAt(0) < 'L') { if (nameStr.equals(AnnotationDefaultAttribute.tag)) return new AnnotationDefaultAttribute(cp, name, in); + else if (nameStr.equals(BootstrapMethodsAttribute.tag)) + return new BootstrapMethodsAttribute(cp, name, in); else if (nameStr.equals(CodeAttribute.tag)) return new CodeAttribute(cp, name, in); else if (nameStr.equals(ConstantAttribute.tag)) diff --git a/src/main/javassist/bytecode/BootstrapMethodsAttribute.java b/src/main/javassist/bytecode/BootstrapMethodsAttribute.java new file mode 100644 index 00000000..98268b87 --- /dev/null +++ b/src/main/javassist/bytecode/BootstrapMethodsAttribute.java @@ -0,0 +1,123 @@ +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Map; + +public class BootstrapMethodsAttribute extends AttributeInfo { + /** + * The name of this attribute "BootstrapMethods". + */ + public static final String tag = "BootstrapMethods"; + + /** + * An element of bootstrap_methods. + */ + public static class BootstrapMethod { + /** + * Constructs an element of bootstrap_methods. + * + * @param method bootstrap_method_ref. + * @param args bootstrap_arguments. + */ + public BootstrapMethod(int method, int[] args) { + methodRef = method; + arguments = args; + } + + /** + * bootstrap_method_ref. + * The value at this index must be a CONSTANT_MethodHandle_info. + */ + public int methodRef; + + /** + * bootstrap_arguments. + */ + public int[] arguments; + } + + BootstrapMethodsAttribute(ConstPool cp, int n, DataInputStream in) + throws IOException + { + super(cp, n, in); + } + + /** + * Constructs a BootstrapMethods attribute. + * + * @param cp a constant pool table. + * @param methods the contents. + */ + public BootstrapMethodsAttribute(ConstPool cp, BootstrapMethod[] methods) { + super(cp, tag); + int size = 2; + for (int i = 0; i < methods.length; i++) + size += 4 + methods[i].arguments.length * 2; + + byte[] data = new byte[size]; + ByteArray.write16bit(methods.length, data, 0); // num_bootstrap_methods + int pos = 2; + for (int i = 0; i < methods.length; i++) { + ByteArray.write16bit(methods[i].methodRef, data, pos); + ByteArray.write16bit(methods[i].arguments.length, data, pos + 2); + int[] args = methods[i].arguments; + pos += 4; + for (int k = 0; k < args.length; k++) { + ByteArray.write16bit(args[k], data, pos); + pos += 2; + } + } + + set(data); + } + + /** + * Obtains bootstrap_methods in this attribute. + * + * @return an array of BootstrapMethod. Since it + * is a fresh copy, modifying the returned array does not + * affect the original contents of this attribute. + */ + public BootstrapMethod[] getMethods() { + byte[] data = this.get(); + int num = ByteArray.readU16bit(data, 0); + BootstrapMethod[] methods = new BootstrapMethod[num]; + int pos = 2; + for (int i = 0; i < num; i++) { + int ref = ByteArray.readU16bit(data, pos); + int len = ByteArray.readU16bit(data, pos + 2); + int[] args = new int[len]; + pos += 4; + for (int k = 0; k < len; k++) { + args[k] = ByteArray.readU16bit(data, pos); + pos += 2; + } + + methods[i] = new BootstrapMethod(ref, args); + } + + return methods; + } + + /** + * Makes a copy. Class names are replaced according to the + * given Map object. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames pairs of replaced and substituted + * class names. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + BootstrapMethod[] methods = getMethods(); + ConstPool thisCp = getConstPool(); + for (int i = 0; i < methods.length; i++) { + BootstrapMethod m = methods[i]; + m.methodRef = thisCp.copy(m.methodRef, newCp, classnames); + for (int k = 0; k < m.arguments.length; k++) + m.arguments[k] = thisCp.copy(m.arguments[k], newCp, classnames); + } + + return new BootstrapMethodsAttribute(newCp, methods); + } +} diff --git a/src/main/javassist/bytecode/Bytecode.java b/src/main/javassist/bytecode/Bytecode.java index 9991a332..bea7b6fa 100644 --- a/src/main/javassist/bytecode/Bytecode.java +++ b/src/main/javassist/bytecode/Bytecode.java @@ -1083,7 +1083,7 @@ public class Bytecode extends ByteVector implements Cloneable, Opcode { public void addInvokevirtual(int clazz, String name, String desc) { add(INVOKEVIRTUAL); addIndex(constPool.addMethodrefInfo(clazz, name, desc)); - growStack(Descriptor.dataSize(desc) - 1); + growStack(Descriptor.dataSize(desc)); // assume CosntPool#REF_invokeStatic } /** @@ -1154,6 +1154,25 @@ public class Bytecode extends ByteVector implements Cloneable, Opcode { growStack(Descriptor.dataSize(desc) - 1); } + /** + * Appends INVOKEDYNAMIC. + * + * @param bootstrap an index into the bootstrap_methods array + * of the bootstrap method table. + * @param name the method name. + * @param desc the method descriptor. + * @see Descriptor#ofMethod(CtClass,CtClass[]) + * @since 3.17 + */ + public void addInvokedynamic(int bootstrap, String name, String desc) { + int nt = constPool.addNameAndTypeInfo(name, desc); + int dyn = constPool.addInvokeDynamicInfo(bootstrap, nt); + add(INVOKEDYNAMIC); + addIndex(dyn); + add(0, 0); + growStack(Descriptor.dataSize(desc) - 1); + } + /** * Appends LDC or LDC_W. The pushed item is a String * object. diff --git a/src/main/javassist/bytecode/CodeAnalyzer.java b/src/main/javassist/bytecode/CodeAnalyzer.java index f8766764..d02fb19e 100644 --- a/src/main/javassist/bytecode/CodeAnalyzer.java +++ b/src/main/javassist/bytecode/CodeAnalyzer.java @@ -240,6 +240,10 @@ class CodeAnalyzer implements Opcode { ci.u16bitAt(index + 1)); stack += Descriptor.dataSize(desc) - 1; break; + case INVOKEDYNAMIC : + desc = constPool.getInvokeDynamicType(ci.u16bitAt(index + 1)); + stack += Descriptor.dataSize(desc); // assume CosntPool#REF_invokeStatic + break; case ATHROW : stack = 1; // the stack becomes empty (1 means no values). break; diff --git a/src/main/javassist/bytecode/CodeAttribute.java b/src/main/javassist/bytecode/CodeAttribute.java index fc4b1d1e..dbf714e7 100644 --- a/src/main/javassist/bytecode/CodeAttribute.java +++ b/src/main/javassist/bytecode/CodeAttribute.java @@ -32,6 +32,7 @@ import java.util.Map; * use CodeIterator. * * @see CodeIterator + * @see #iterator() */ public class CodeAttribute extends AttributeInfo implements Opcode { /** @@ -400,6 +401,12 @@ public class CodeAttribute extends AttributeInfo implements Opcode { newcode[i + 3] = code[i + 3]; newcode[i + 4] = code[i + 4]; break; + case INVOKEDYNAMIC : + copyConstPoolInfo(i + 1, code, srcCp, newcode, destCp, + classnameMap); + newcode[i + 3] = 0; + newcode[i + 4] = 0; + break; case MULTIANEWARRAY : copyConstPoolInfo(i + 1, code, srcCp, newcode, destCp, classnameMap); diff --git a/src/main/javassist/bytecode/CodeIterator.java b/src/main/javassist/bytecode/CodeIterator.java index 68abd55a..0c5db6ff 100644 --- a/src/main/javassist/bytecode/CodeIterator.java +++ b/src/main/javassist/bytecode/CodeIterator.java @@ -21,6 +21,16 @@ import java.util.ArrayList; /** * An iterator for editing a code attribute. * + *

To directly read or edit a bytecode sequence, call {@link #byteAt(int)}, {@link #s16bitAt(int)}, + * {@link #writeByte(int, int)}, {@link #write16bit(int, int)}, and other methods. + * For example, if method refers to a CtMethod object, + * the following code substitutes the NOP instruction for the first + * instruction of the method: + * + *

CodeAttribute ca = method.getMethodInfo().getCodeAttribute();
+ * CodeIterator ci = ca.iterator();
+ * ci.writeByte(Opcode.NOP, 0);
+ * *

If there are multiple CodeIterators referring to the * same Code_attribute, then inserting a gap by one * CodeIterator will break the other @@ -725,11 +735,10 @@ public class CodeIterator implements Opcode { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 0, 0, 1, 1, 1, 1, 1, 1, 3, 3, - // 3, 3, 3, 3, 3, 5, 5, 3, 2, 3, 1, 1, 3, 3, 1, 1, 0, 4, 3, 3, - 3, 3, 3, 3, 3, 5, 0, 3, 2, 3, 1, 1, 3, 3, 1, 1, 0, 4, 3, 3, + 3, 3, 3, 3, 3, 5, 5, 3, 2, 3, 1, 1, 3, 3, 1, 1, 0, 4, 3, 3, 5, 5 }; - // 0 .. UNUSED (186), LOOKUPSWITCH, TABLESWITCH, WIDE + // 0 .. LOOKUPSWITCH, TABLESWITCH, WIDE /** * Calculates the index of the next opcode. @@ -1496,7 +1505,9 @@ public class CodeIterator implements Opcode { int padding = 3 - (pos & 3); int nops = gap - padding; int bytecodeSize = 5 + (3 - (orgPos & 3)) + tableSize(); - adjustOffsets(bytecodeSize, nops); + if (nops > 0) + adjustOffsets(bytecodeSize, nops); + newcode[dest++] = code[src]; while (padding-- > 0) newcode[dest++] = 0; diff --git a/src/main/javassist/bytecode/ConstPool.java b/src/main/javassist/bytecode/ConstPool.java index 27124ac1..f1024216 100644 --- a/src/main/javassist/bytecode/ConstPool.java +++ b/src/main/javassist/bytecode/ConstPool.java @@ -93,11 +93,61 @@ public final class ConstPool { */ public static final int CONST_Utf8 = Utf8Info.tag; + /** + * Cosnt_MethodHandle + */ + public static final int CONST_MethodHandle = MethodHandleInfo.tag; + /** * Represents the class using this constant pool table. */ public static final CtClass THIS = null; + /** + * reference_kind of CONSTANT_MethodHandle_info. + */ + public static final int REF_getField = 1; + + /** + * reference_kind of CONSTANT_MethodHandle_info. + */ + public static final int REF_getStatic = 2; + + /** + * reference_kind of CONSTANT_MethodHandle_info. + */ + public static final int REF_putField = 3; + + /** + * reference_kind of CONSTANT_MethodHandle_info. + */ + public static final int REF_putStatic = 4; + + /** + * reference_kind of CONSTANT_MethodHandle_info. + */ + public static final int REF_invokeVirtual = 5; + + /** + * reference_kind of CONSTANT_MethodHandle_info. + */ + public static final int REF_invokeStatic = 6; + + /** + * reference_kind of CONSTANT_MethodHandle_info. + */ + public static final int REF_invokeSpecial = 7; + + /** + * reference_kind of CONSTANT_MethodHandle_info. + */ + public static final int REF_newInvokeSpecial = 8; + + /** + * reference_kind of CONSTANT_MethodHandle_info. + */ + public static final int REF_invokeInterface = 9; + /** * Constructs a constant pool table. * @@ -588,6 +638,97 @@ public final class ConstPool { return utf.string; } + /** + * Reads the reference_kind field of the + * CONSTANT_MethodHandle_info structure + * at the given index. + * + * @see #REF_getField + * @see #REF_getStatic + * @see #REF_invokeInterface + * @see #REF_invokeSpecial + * @see #REF_invokeStatic + * @see #REF_invokeVirtual + * @see #REF_newInvokeSpecial + * @see #REF_putField + * @see #REF_putStatic + * @since 3.17 + */ + public int getMethodHandleKind(int index) { + MethodHandleInfo mhinfo = (MethodHandleInfo)getItem(index); + return mhinfo.refKind; + } + + /** + * Reads the reference_index field of the + * CONSTANT_MethodHandle_info structure + * at the given index. + * + * @since 3.17 + */ + public int getMethodHandleIndex(int index) { + MethodHandleInfo mhinfo = (MethodHandleInfo)getItem(index); + return mhinfo.refIndex; + } + + /** + * Reads the descriptor_index field of the + * CONSTANT_MethodType_info structure + * at the given index. + * + * @since 3.17 + */ + public int getMethodTypeInfo(int index) { + MethodTypeInfo mtinfo = (MethodTypeInfo)getItem(index); + return mtinfo.descriptor; + } + + /** + * Reads the bootstrap_method_attr_index field of the + * CONSTANT_InvokeDynamic_info structure + * at the given index. + * + * @since 3.17 + */ + public int getInvokeDynamicBootstrap(int index) { + InvokeDynamicInfo iv = (InvokeDynamicInfo)getItem(index); + return iv.bootstrap; + } + + /** + * Reads the name_and_type_index field of the + * CONSTANT_InvokeDynamic_info structure + * at the given index. + * + * @since 3.17 + */ + public int getInvokeDynamicNameAndType(int index) { + InvokeDynamicInfo iv = (InvokeDynamicInfo)getItem(index); + return iv.nameAndType; + } + + /** + * Reads the descriptor_index field of the + * CONSTANT_NameAndType_info structure + * indirectly specified by the given index. + * + * @param index an index to a CONSTANT_InvokeDynamic_info. + * @return the descriptor of the method. + * @since 3.17 + */ + public String getInvokeDynamicType(int index) { + InvokeDynamicInfo iv = (InvokeDynamicInfo)getItem(index); + if (iv == null) + return null; + else { + NameAndTypeInfo n = (NameAndTypeInfo)getItem(iv.nameAndType); + if(n == null) + return null; + else + return getUtf8Info(n.typeDescriptor); + } + } + /** * Determines whether CONSTANT_Methodref_info * structure at the given index represents the constructor @@ -926,6 +1067,48 @@ public final class ConstPool { return addItem(new Utf8Info(utf8, numOfItems)); } + /** + * Adds a new CONSTANT_MethodHandle_info + * structure. + * + * @param kind reference_kind + * such as {@link #REF_invokeStatic REF_invokeStatic}. + * @param index reference_index. + * @return the index of the added entry. + * + * @since 3.17 + */ + public int addMethodHandleInfo(int kind, int index) { + return addItem(new MethodHandleInfo(kind, index, numOfItems)); + } + + /** + * Adds a new CONSTANT_MethodType_info + * structure. + * + * @param desc descriptor_index. + * @return the index of the added entry. + * + * @since 3.17 + */ + public int addMethodTypeInfo(int desc) { + return addItem(new MethodTypeInfo(desc, numOfItems)); + } + + /** + * Adds a new CONSTANT_InvokeDynamic_info + * structure. + * + * @param bootstrap bootstrap_method_attr_index. + * @param nameAndType name_and_type_index. + * @return the index of the added entry. + * + * @since 3.17 + */ + public int addInvokeDynamicInfo(int bootstrap, int nameAndType) { + return addItem(new InvokeDynamicInfo(bootstrap, nameAndType, numOfItems)); + } + /** * Get all the class names. * @@ -1040,6 +1223,15 @@ public final class ConstPool { case NameAndTypeInfo.tag : // 12 info = new NameAndTypeInfo(in, numOfItems); break; + case MethodHandleInfo.tag : // 15 + info = new MethodHandleInfo(in, numOfItems); + break; + case MethodTypeInfo.tag : // 16 + info = new MethodTypeInfo(in, numOfItems); + break; + case InvokeDynamicInfo.tag : // 18 + info = new InvokeDynamicInfo(in, numOfItems); + break; default : throw new IOException("invalid constant type: " + tag + " at " + numOfItems); } @@ -1632,3 +1824,167 @@ class Utf8Info extends ConstInfo { out.println("\""); } } + +class MethodHandleInfo extends ConstInfo { + static final int tag = 15; + int refKind, refIndex; + + public MethodHandleInfo(int kind, int referenceIndex, int index) { + super(index); + refKind = kind; + refIndex = referenceIndex; + } + + public MethodHandleInfo(DataInputStream in, int index) throws IOException { + super(index); + refKind = in.readUnsignedByte(); + refIndex = in.readUnsignedShort(); + } + + public int hashCode() { return (refKind << 16) ^ refIndex; } + + public boolean equals(Object obj) { + if (obj instanceof MethodHandleInfo) { + MethodHandleInfo mh = (MethodHandleInfo)obj; + return mh.refKind == refKind && mh.refIndex == refIndex; + } + else + return false; + } + + public int getTag() { return tag; } + + public int copy(ConstPool src, ConstPool dest, Map map) { + return dest.addMethodHandleInfo(refKind, + src.getItem(refIndex).copy(src, dest, map)); + } + + public void write(DataOutputStream out) throws IOException { + out.writeByte(tag); + out.writeByte(refKind); + out.writeShort(refIndex); + } + + public void print(PrintWriter out) { + out.print("MethodHandle #"); + out.print(refKind); + out.print(", index #"); + out.println(refIndex); + } +} + +class MethodTypeInfo extends ConstInfo { + static final int tag = 16; + int descriptor; + + public MethodTypeInfo(int desc, int index) { + super(index); + descriptor = desc; + } + + public MethodTypeInfo(DataInputStream in, int index) throws IOException { + super(index); + descriptor = in.readUnsignedShort(); + } + + public int hashCode() { return descriptor; } + + public boolean equals(Object obj) { + if (obj instanceof MethodTypeInfo) + return ((MethodTypeInfo)obj).descriptor == descriptor; + else + return false; + } + + public int getTag() { return tag; } + + public void renameClass(ConstPool cp, String oldName, String newName, HashMap cache) { + String desc = cp.getUtf8Info(descriptor); + String desc2 = Descriptor.rename(desc, oldName, newName); + if (desc != desc2) + if (cache == null) + descriptor = cp.addUtf8Info(desc2); + else { + cache.remove(this); + descriptor = cp.addUtf8Info(desc2); + cache.put(this, this); + } + } + + public void renameClass(ConstPool cp, Map map, HashMap cache) { + String desc = cp.getUtf8Info(descriptor); + String desc2 = Descriptor.rename(desc, map); + if (desc != desc2) + if (cache == null) + descriptor = cp.addUtf8Info(desc2); + else { + cache.remove(this); + descriptor = cp.addUtf8Info(desc2); + cache.put(this, this); + } + } + + public int copy(ConstPool src, ConstPool dest, Map map) { + String desc = src.getUtf8Info(descriptor); + desc = Descriptor.rename(desc, map); + return dest.addMethodTypeInfo(dest.addUtf8Info(desc)); + } + + public void write(DataOutputStream out) throws IOException { + out.writeByte(tag); + out.writeShort(descriptor); + } + + public void print(PrintWriter out) { + out.print("MethodType #"); + out.println(descriptor); + } +} + +class InvokeDynamicInfo extends ConstInfo { + static final int tag = 18; + int bootstrap, nameAndType; + + public InvokeDynamicInfo(int bootstrapMethod, int ntIndex, int index) { + super(index); + bootstrap = bootstrapMethod; + nameAndType = ntIndex; + } + + public InvokeDynamicInfo(DataInputStream in, int index) throws IOException { + super(index); + bootstrap = in.readUnsignedShort(); + nameAndType = in.readUnsignedShort(); + } + + public int hashCode() { return (bootstrap << 16) ^ nameAndType; } + + public boolean equals(Object obj) { + if (obj instanceof InvokeDynamicInfo) { + InvokeDynamicInfo iv = (InvokeDynamicInfo)obj; + return iv.bootstrap == bootstrap && iv.nameAndType == nameAndType; + } + else + return false; + } + + public int getTag() { return tag; } + + public int copy(ConstPool src, ConstPool dest, Map map) { + return dest.addInvokeDynamicInfo(bootstrap, + src.getItem(nameAndType).copy(src, dest, map)); + } + + public void write(DataOutputStream out) throws IOException { + out.writeByte(tag); + out.writeShort(bootstrap); + out.writeShort(nameAndType); + } + + public void print(PrintWriter out) { + out.print("InvokeDynamic #"); + out.print(bootstrap); + out.print(", name&type #"); + out.println(nameAndType); + } +} diff --git a/src/main/javassist/bytecode/InstructionPrinter.java b/src/main/javassist/bytecode/InstructionPrinter.java index c02767d9..12f74d5b 100644 --- a/src/main/javassist/bytecode/InstructionPrinter.java +++ b/src/main/javassist/bytecode/InstructionPrinter.java @@ -137,8 +137,8 @@ public class InstructionPrinter implements Opcode { return opstring + " " + methodInfo(pool, iter.u16bitAt(pos + 1)); case INVOKEINTERFACE: return opstring + " " + interfaceMethodInfo(pool, iter.u16bitAt(pos + 1)); - case 186: - throw new RuntimeException("Bad opcode 186"); + case INVOKEDYNAMIC: + return opstring + " " + iter.u16bitAt(pos + 1); case NEW: return opstring + " " + classInfo(pool, iter.u16bitAt(pos + 1)); case NEWARRAY: diff --git a/src/main/javassist/bytecode/MethodInfo.java b/src/main/javassist/bytecode/MethodInfo.java index 411e5549..4ca4e90d 100644 --- a/src/main/javassist/bytecode/MethodInfo.java +++ b/src/main/javassist/bytecode/MethodInfo.java @@ -27,7 +27,12 @@ import javassist.bytecode.stackmap.MapMaker; /** * method_info structure. + * + *

The bytecode sequence of the method is represented + * by a CodeAttribute object. * + * @see #getCodeAttribute() + * @see CodeAttribute * @see javassist.CtMethod#getMethodInfo() * @see javassist.CtConstructor#getMethodInfo() */ @@ -390,6 +395,7 @@ public class MethodInfo { * @param cf rebuild if this class file is for Java 6 or later. * @see #rebuildStackMap(ClassPool) * @see #rebuildStackMapForME(ClassPool) + * @see #doPreverify * @since 3.6 */ public void rebuildStackMapIf6(ClassPool pool, ClassFile cf) diff --git a/src/main/javassist/bytecode/Mnemonic.java b/src/main/javassist/bytecode/Mnemonic.java index 7846ea9b..0872655b 100644 --- a/src/main/javassist/bytecode/Mnemonic.java +++ b/src/main/javassist/bytecode/Mnemonic.java @@ -31,9 +31,6 @@ public interface Mnemonic { /** * The instruction names (mnemonics) sorted by the opcode. * The length of this array is 202 (jsr_w=201). - * - *

The value at index 186 is null since no instruction is - * assigned to 186. */ String[] OPCODE = { "nop", /* 0*/ @@ -222,7 +219,7 @@ public interface Mnemonic { "invokespecial", /* 183*/ "invokestatic", /* 184*/ "invokeinterface", /* 185*/ - null, + "invokedynamic", /* 186 */ "new", /* 187*/ "newarray", /* 188*/ "anewarray", /* 189*/ diff --git a/src/main/javassist/bytecode/Opcode.java b/src/main/javassist/bytecode/Opcode.java index 363dbe23..54555bd1 100644 --- a/src/main/javassist/bytecode/Opcode.java +++ b/src/main/javassist/bytecode/Opcode.java @@ -156,6 +156,7 @@ public interface Opcode { int IMUL = 104; int INEG = 116; int INSTANCEOF = 193; + int INVOKEDYNAMIC = 186; int INVOKEINTERFACE = 185; int INVOKESPECIAL = 183; int INVOKESTATIC = 184; @@ -428,7 +429,7 @@ public interface Opcode { 0, // invokespecial, 183 depends on the type 0, // invokestatic, 184 depends on the type 0, // invokeinterface, 185 depends on the type - 0, // undefined, 186 + 0, // invokedynaimc, 186 depends on the type 1, // new, 187 0, // newarray, 188 0, // anewarray, 189 diff --git a/src/main/javassist/bytecode/StackMapTable.java b/src/main/javassist/bytecode/StackMapTable.java index 4518ef36..0ea3e089 100644 --- a/src/main/javassist/bytecode/StackMapTable.java +++ b/src/main/javassist/bytecode/StackMapTable.java @@ -909,6 +909,8 @@ public class StackMapTable extends AttributeInfo { newDelta = offsetDelta - gap; else if (where == oldPos) newDelta = offsetDelta + gap; + // else if (gap > 0 && oldPos < where && where < position) // chiba + // throw new RuntimeException("old:" + oldPos + " where:" + where + " pos:" + position); else return; @@ -949,6 +951,8 @@ public class StackMapTable extends AttributeInfo { newDelta = offsetDelta - gap; else if (where == oldPos) newDelta = offsetDelta + gap; + // else if (gap > 0 && oldPos < where && where < position) // chiba + // throw new RuntimeException("old:" + oldPos + " where:" + where + " pos:" + position); else return; diff --git a/src/main/javassist/bytecode/analysis/Executor.java b/src/main/javassist/bytecode/analysis/Executor.java index f53e98bf..63c4c7c7 100644 --- a/src/main/javassist/bytecode/analysis/Executor.java +++ b/src/main/javassist/bytecode/analysis/Executor.java @@ -573,8 +573,9 @@ public class Executor implements Opcode { case INVOKEINTERFACE: evalInvokeIntfMethod(opcode, iter.u16bitAt(pos + 1), frame); break; - case 186: - throw new RuntimeException("Bad opcode 186"); + case INVOKEDYNAMIC: + evalInvokeDynamic(opcode, iter.u16bitAt(pos + 1), frame); + break; case NEW: frame.push(resolveClassInfo(constPool.getClassInfo(iter.u16bitAt(pos + 1)))); break; @@ -748,6 +749,20 @@ public class Executor implements Opcode { simplePush(zeroExtend(returnType), frame); } + private void evalInvokeDynamic(int opcode, int index, Frame frame) throws BadBytecode { + String desc = constPool.getInvokeDynamicType(index); + Type[] types = paramTypesFromDesc(desc); + int i = types.length; + + while (i > 0) + verifyAssignable(zeroExtend(types[--i]), simplePop(frame)); + + // simplePop(frame); // assume CosntPool#REF_invokeStatic + + Type returnType = returnTypeFromDesc(desc); + if (returnType != Type.VOID) + simplePush(zeroExtend(returnType), frame); + } private void evalLDC(int index, Frame frame) throws BadBytecode { int tag = constPool.getTag(index); diff --git a/src/main/javassist/bytecode/stackmap/Tracer.java b/src/main/javassist/bytecode/stackmap/Tracer.java index 8a5aa97d..1bacd09c 100644 --- a/src/main/javassist/bytecode/stackmap/Tracer.java +++ b/src/main/javassist/bytecode/stackmap/Tracer.java @@ -597,8 +597,8 @@ public abstract class Tracer implements TypeTag { return doInvokeMethod(pos, code, false); case Opcode.INVOKEINTERFACE : return doInvokeIntfMethod(pos, code); - case 186 : - throw new RuntimeException("bad opcode 186"); + case Opcode.INVOKEDYNAMIC : + return doInvokeDynamic(pos, code); case Opcode.NEW : { int i = ByteArray.readU16bit(code, pos + 1); stackTypes[stackTop++] @@ -835,6 +835,21 @@ public abstract class Tracer implements TypeTag { return 5; } + private int doInvokeDynamic(int pos, byte[] code) throws BadBytecode { + int i = ByteArray.readU16bit(code, pos + 1); + String desc = cpool.getInvokeDynamicType(i); + checkParamTypes(desc, 1); + + // assume CosntPool#REF_invokeStatic + /* TypeData target = stackTypes[--stackTop]; + if (target instanceof TypeData.UninitTypeVar && target.isUninit()) + constructorCalled((TypeData.UninitTypeVar)target); + */ + + pushMemberType(desc); + return 5; + } + private void pushMemberType(String descriptor) { int top = 0; if (descriptor.charAt(0) == '(') { diff --git a/src/test/javassist/JvstTest4.java b/src/test/javassist/JvstTest4.java index 3ff6927d..76f139bd 100644 --- a/src/test/javassist/JvstTest4.java +++ b/src/test/javassist/JvstTest4.java @@ -662,6 +662,7 @@ public class JvstTest4 extends JvstTestRoot { } public void testJIRA150b() throws Exception { + int origSize = javassist.compiler.MemberResolver.getInvalidMapSize(); int N = 100; for (int k = 0; k < N; k++) { ClassPool pool = new ClassPool(true); @@ -681,12 +682,20 @@ public class JvstTest4 extends JvstTestRoot { " int n5 = java.lang.Integer#valueOf(5); " + " return n1+n2+n3+n4+n5; }"); } + pool = null; + } + + // try to run garbage collection. + int[] large; + for (int i = 0; i < 100; i++) { + large = new int[1000000]; + large[large.length - 2] = 9; } System.gc(); System.gc(); int size = javassist.compiler.MemberResolver.getInvalidMapSize(); System.out.println("JIRA150b " + size); - assertTrue("JIRA150b size: " + size, size < N - 10); + assertTrue("JIRA150b size: " + origSize + " " + size, size < origSize + N); } public void testJIRA152() throws Exception { diff --git a/src/test/javassist/bytecode/BytecodeTest.java b/src/test/javassist/bytecode/BytecodeTest.java index c31e3965..e37529a0 100644 --- a/src/test/javassist/bytecode/BytecodeTest.java +++ b/src/test/javassist/bytecode/BytecodeTest.java @@ -779,6 +779,52 @@ public class BytecodeTest extends TestCase { assertEquals("[Ltest.Bar2;", cp.getClassInfo(n8)); } + public void testInvokeDynamic() throws Exception { + CtClass cc = loader.get("test4.InvokeDyn"); + ClassFile cf = cc.getClassFile(); + ConstPool cp = cf.getConstPool(); + + Bytecode code = new Bytecode(cp, 0, 1); + code.addAload(0); + code.addIconst(9); + code.addLdc("nine"); + code.addInvokedynamic(0, "call", "(ILjava/lang/String;)I"); + code.addOpcode(Opcode.SWAP); + code.addOpcode(Opcode.POP); + code.addOpcode(Opcode.IRETURN); + + MethodInfo minfo = new MethodInfo(cp, "test", "()I"); + minfo.setCodeAttribute(code.toCodeAttribute()); + minfo.setAccessFlags(AccessFlag.PUBLIC); + minfo.rebuildStackMapIf6(loader, cf); + cf.addMethod(minfo); + + cf.addMethod(new MethodInfo(cp, "test2", minfo, null)); + int mtIndex = cp.addMethodTypeInfo(cp.addUtf8Info("(I)V")); + + String desc + = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)" + + "Ljava/lang/invoke/CallSite;"; + int mri = cp.addMethodrefInfo(cp.addClassInfo(cc.getName()), "boot", desc); + int mhi = cp.addMethodHandleInfo(ConstPool.REF_invokeStatic, mri); + int[] args = new int[0]; + BootstrapMethodsAttribute.BootstrapMethod[] bms + = new BootstrapMethodsAttribute.BootstrapMethod[1]; + bms[0] = new BootstrapMethodsAttribute.BootstrapMethod(mhi, args); + + cf.addAttribute(new BootstrapMethodsAttribute(cp, bms)); + cc.writeFile(); + Object obj = make(cc.getName()); + assertEquals(9, invoke(obj, "test")); + + ClassPool cp2 = new ClassPool(); + cp2.appendClassPath("."); + CtClass cc2 = cp2.get(cc.getName()); + assertEquals("test4.InvokeDyn", cc2.getClassFile().getName()); + ConstPool cPool2 = cc2.getClassFile().getConstPool(); + assertEquals("(I)V", cPool2.getUtf8Info(cPool2.getMethodTypeInfo(mtIndex))); + } + public static void main(String[] args) { // junit.textui.TestRunner.run(suite()); junit.awtui.TestRunner.main(new String[] { diff --git a/src/test/javassist/bytecode/StackMapTest.java b/src/test/javassist/bytecode/StackMapTest.java index c7a77cb8..4b9339d7 100644 --- a/src/test/javassist/bytecode/StackMapTest.java +++ b/src/test/javassist/bytecode/StackMapTest.java @@ -423,6 +423,171 @@ public class StackMapTest extends TestCase { } } + public void testSwitchCase3() throws Exception { + CtClass cc = loader.get("javassist.bytecode.StackMapTest$T7c"); + StringBuffer sbuf = new StringBuffer("String s;"); + for (int i = 0; i < 130; i++) + sbuf.append("s =\"" + i + "\";"); + + cc.getDeclaredMethod("foo").insertBefore(sbuf.toString()); + cc.getDeclaredMethod("test2").setBody(loader.get("javassist.bytecode.StackMapTest$T8c").getDeclaredMethod("test2"), null); + + cc.writeFile(); + Object t1 = make(cc.getName()); + assertEquals(100, invoke(t1, "test")); + } + + public static class T7c { + int value = 1; + T7b t7; + public static T7b make2() { return null; } + public static void print(String s) {} + public int foo() { return 1; } + public int test() { return test2(10); } + public int test2(int k) { return 0; } + } + + public static class T8c { + public int test2(int k) { + int jj = 50; + if (k > 0) + k += "fooo".length(); + + int j = 50; + loop: for (int i = 0; i < 10; i++) { + int jjj = 1; + switch (i) { + case 0: + k++; + { int poi = 0; } + break; + case 9: + break loop; + case 7: + k += jjj; + break; + } + } + return j + jj; + } + } + + public void testSwitchCase4() throws Exception { + CtClass cc = loader.get("javassist.bytecode.StackMapTest$T8e"); + StringBuffer sbuf = new StringBuffer("String s;"); + for (int i = 0; i < 130; i++) + sbuf.append("s =\"" + i + "\";"); + + cc.getDeclaredMethod("foo").insertBefore(sbuf.toString()); + CtClass orig = loader.get("javassist.bytecode.StackMapTest$T8d"); + CtMethod origM = orig.getDeclaredMethod("test2"); + writeLdcw(origM); + cc.getDeclaredMethod("test2").setBody(origM, null); + + orig.writeFile(); + cc.writeFile(); + Object t1 = make(cc.getName()); + assertEquals(100, invoke(t1, "test")); + } + + private void writeLdcw(CtMethod method) { + CodeAttribute ca = method.getMethodInfo().getCodeAttribute(); + int index = ca.getConstPool().addStringInfo("ldcw"); + CodeIterator ci = ca.iterator(); + ci.writeByte(Opcode.LDC_W, 14); + ci.write16bit(index, 15); + ci.writeByte(Opcode.LDC_W, 43); + ci.write16bit(index, 44); + } + + public static class T8e { + static T8dd helper() { return new T8dd(); } + int integer() { return 9; } + boolean integer2(String s) { return true; } + static void print(String s) {} + private void print2(String s) {} + private Object func(String s) { return null; } + I8d func2(String s) { return null; } + static String ldcw() { return "k"; } + static boolean debug = false; + void log(String p1,String p2,String p3,Long p4, Object p5, Object p6) {} + + public int foo() { return 1; } + public int test() { + try { + return test2("", 10) ? 1 : 0; + } catch (Exception e) {} + return 100; + } + public boolean test2(String s, int k) throws Exception { return false; } + } + + public static interface I8d { + String foo(String s, int i); + } + + public static class T8dd { + java.util.List foo(String s) { return new java.util.ArrayList(); } + } + public static class T8d { + static T8dd helper() { return new T8dd(); } + int integer() { return 9; } + boolean integer2(String s) { return true; } + static void print(String s) {} + private void print2(String s) {} + private Object func(String s) { return null; } + I8d func2(String s) { return null; } + static String ldcw() { return "k"; } + static boolean debug = false; + void log(String p1,String p2,String p3,Long p4, Object p5, Object p6) {} + + public boolean test2(String s, int i) throws Exception { + String k = null; + Object k2 = null; + boolean v5 = true; + if (!debug) + print(ldcw()); + + Object k3 = this.func(s); + if (k3 == null) + throw new Exception(new StringBuilder().append(ldcw()).append(s).append(",").toString()); + + String v7 = k3.toString(); + k2 = this.func2(v7); + if (k2 != null) { // 82: + if (k2 instanceof I8d) { + I8d k5 = (I8d)k2; + k = k5.foo(s, i); + } + } + + java.util.List list = helper().foo(v7); // local 8 + for (int v9 = 0; v9 < list.size(); v9++) { + boolean v10 = true; + T8d v11 = (T8d)list.get(v9); + switch (v11.integer()) { + case 1: + break; + case 2: + v10 = this.integer2(s); + v5 &= v10; + break; + default : + throw new Exception(new StringBuilder().append("ldc 189").append(v11.integer()).append("ldc 169").toString()); + } + } + + if (v5) // 246: + this.print2(s); + if (v5) + this.log(ldcw(), v7, s, Long.valueOf(new Integer(i).longValue()), k, null); + else // 290: + this.log(ldcw(), v7, s, Long.valueOf(new Integer(i).longValue()), k, null); + + return v5; + } + } + public void tstCtClassType() throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass cc = cp.get("javassist.CtClassType"); diff --git a/src/test/test4/InvokeDyn.java b/src/test/test4/InvokeDyn.java new file mode 100644 index 00000000..5e5f0c01 --- /dev/null +++ b/src/test/test4/InvokeDyn.java @@ -0,0 +1,14 @@ +package test4; + +import java.lang.invoke.*; + +public class InvokeDyn { + public static int test9(int i, String s) { return 9; } + + public static CallSite boot(MethodHandles.Lookup caller, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + Class thisClass = lookup.lookupClass(); + MethodHandle method = lookup.findStatic(thisClass, "test9", MethodType.methodType(int.class, int.class, String.class)); + return new ConstantCallSite(method); + } +} -- 2.39.5