diff --git a/spring-core/src/main/java/org/springframework/asm/AnnotationVisitor.java b/spring-core/src/main/java/org/springframework/asm/AnnotationVisitor.java index 5f48e99324..c2d30f588c 100644 --- a/spring-core/src/main/java/org/springframework/asm/AnnotationVisitor.java +++ b/spring-core/src/main/java/org/springframework/asm/AnnotationVisitor.java @@ -89,7 +89,7 @@ public abstract class AnnotationVisitor { * the actual value, whose type must be {@link Byte}, * {@link Boolean}, {@link Character}, {@link Short}, * {@link Integer} , {@link Long}, {@link Float}, {@link Double}, - * {@link String} or {@link Type} or OBJECT or ARRAY sort. This + * {@link String} or {@link Type} of OBJECT or ARRAY sort. This * value can also be an array of byte, boolean, short, char, int, * long, float or double values (this is equivalent to using * {@link #visitArray visitArray} and visiting each array element diff --git a/spring-core/src/main/java/org/springframework/asm/ClassReader.java b/spring-core/src/main/java/org/springframework/asm/ClassReader.java index 29520b40ff..9cca0b5a0a 100644 --- a/spring-core/src/main/java/org/springframework/asm/ClassReader.java +++ b/spring-core/src/main/java/org/springframework/asm/ClassReader.java @@ -104,6 +104,21 @@ public class ClassReader { */ public static final int EXPAND_FRAMES = 8; + /** + * Flag to expand the ASM pseudo instructions into an equivalent sequence of + * standard bytecode instructions. When resolving a forward jump it may + * happen that the signed 2 bytes offset reserved for it is not sufficient + * to store the bytecode offset. In this case the jump instruction is + * replaced with a temporary ASM pseudo instruction using an unsigned 2 + * bytes offset (see Label#resolve). This internal flag is used to re-read + * classes containing such instructions, in order to replace them with + * standard instructions. In addition, when this flag is used, GOTO_W and + * JSR_W are not converted into GOTO and JSR, to make sure that + * infinite loops where a GOTO_W is replaced with a GOTO in ClassReader and + * converted back to a GOTO_W in ClassWriter cannot occur. + */ + static final int EXPAND_ASM_INSNS = 256; + /** * The class to be parsed. The content of this array must not be * modified. This field is intended for {@link Attribute} sub classes, and @@ -1062,6 +1077,10 @@ public class ClassReader { readLabel(offset + readShort(u + 1), labels); u += 3; break; + case ClassWriter.ASM_LABEL_INSN: + readLabel(offset + readUnsignedShort(u + 1), labels); + u += 3; + break; case ClassWriter.LABELW_INSN: readLabel(offset + readInt(u + 1), labels); u += 5; @@ -1286,8 +1305,23 @@ public class ClassReader { } } } + if ((context.flags & EXPAND_ASM_INSNS) != 0) { + // Expanding the ASM pseudo instructions can introduce F_INSERT + // frames, even if the method does not currently have any frame. + // Also these inserted frames must be computed by simulating the + // effect of the bytecode instructions one by one, starting from the + // first one and the last existing frame (or the implicit first + // one). Finally, due to the way MethodWriter computes this (with + // the compute = INSERTED_FRAMES option), MethodWriter needs to know + // maxLocals before the first instruction is visited. For all these + // reasons we always visit the implicit first frame in this case + // (passing only maxLocals - the rest can be and is computed in + // MethodWriter). + mv.visitFrame(Opcodes.F_NEW, maxLocals, null, 0, null); + } // visits the instructions + int opcodeDelta = (context.flags & EXPAND_ASM_INSNS) == 0 ? -33 : 0; u = codeStart; while (u < codeEnd) { int offset = u - codeStart; @@ -1352,9 +1386,42 @@ public class ClassReader { u += 3; break; case ClassWriter.LABELW_INSN: - mv.visitJumpInsn(opcode - 33, labels[offset + readInt(u + 1)]); + mv.visitJumpInsn(opcode + opcodeDelta, labels[offset + + readInt(u + 1)]); u += 5; break; + case ClassWriter.ASM_LABEL_INSN: { + // changes temporary opcodes 202 to 217 (inclusive), 218 + // and 219 to IFEQ ... JSR (inclusive), IFNULL and + // IFNONNULL + opcode = opcode < 218 ? opcode - 49 : opcode - 20; + Label target = labels[offset + readUnsignedShort(u + 1)]; + // replaces GOTO with GOTO_W, JSR with JSR_W and IFxxx + // with IFNOTxxx GOTO_W , where IFNOTxxx is + // the "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) + // and where designates the instruction just after + // the GOTO_W. + if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) { + mv.visitJumpInsn(opcode + 33, target); + } else { + opcode = opcode <= 166 ? ((opcode + 1) ^ 1) - 1 + : opcode ^ 1; + Label endif = new Label(); + mv.visitJumpInsn(opcode, endif); + mv.visitJumpInsn(200, target); // GOTO_W + mv.visitLabel(endif); + // since we introduced an unconditional jump instruction we + // also need to insert a stack map frame here, unless there + // is already one. The actual frame content will be computed + // in MethodWriter. + if (FRAMES && stackMap != 0 + && (frame == null || frame.offset != offset + 3)) { + mv.visitFrame(ClassWriter.F_INSERT, 0, null, 0, null); + } + } + u += 3; + break; + } case ClassWriter.WIDE_INSN: opcode = b[u + 1] & 0xFF; if (opcode == Opcodes.IINC) { @@ -1848,7 +1915,9 @@ public class ClassReader { v += 2; break; case 'Z': // pointer to CONSTANT_Boolean - av.visit(name, readInt(items[readUnsignedShort(v)]) == 0 ? Boolean.FALSE : Boolean.TRUE); + av.visit(name, + readInt(items[readUnsignedShort(v)]) == 0 ? Boolean.FALSE + : Boolean.TRUE); v += 2; break; case 'S': // pointer to CONSTANT_Short diff --git a/spring-core/src/main/java/org/springframework/asm/ClassWriter.java b/spring-core/src/main/java/org/springframework/asm/ClassWriter.java index 72add9d60e..1393c78e15 100644 --- a/spring-core/src/main/java/org/springframework/asm/ClassWriter.java +++ b/spring-core/src/main/java/org/springframework/asm/ClassWriter.java @@ -58,8 +58,8 @@ public class ClassWriter extends ClassVisitor { * {@link MethodVisitor#visitFrame} method are ignored, and the stack map * frames are recomputed from the methods bytecode. The arguments of the * {@link MethodVisitor#visitMaxs visitMaxs} method are also ignored and - * recomputed from the bytecode. In other words, computeFrames implies - * computeMaxs. + * recomputed from the bytecode. In other words, COMPUTE_FRAMES implies + * COMPUTE_MAXS. * * @see #ClassWriter(int) */ @@ -167,6 +167,22 @@ public class ClassWriter extends ClassVisitor { */ static final int WIDE_INSN = 17; + /** + * The type of the ASM pseudo instructions with an unsigned 2 bytes offset + * label (see Label#resolve). + */ + static final int ASM_LABEL_INSN = 18; + + /** + * Represents a frame inserted between already existing frames. This kind of + * frame can only be used if the frame content can be computed from the + * previous existing frame and from the instructions between this existing + * frame and the inserted one, without any knowledge of the type hierarchy. + * This kind of frame is only used when an unconditional jump is inserted in + * a method while expanding an ASM pseudo instruction (see ClassReader). + */ + static final int F_INSERT = 256; + /** * The instruction types of all JVM opcodes. */ @@ -484,25 +500,19 @@ public class ClassWriter extends ClassVisitor { MethodWriter lastMethod; /** - * true if the maximum stack size and number of local variables - * must be automatically computed. + * Indicates what must be automatically computed. + * + * @see MethodWriter#compute */ - private boolean computeMaxs; + private int compute; /** - * true if the stack map frames must be recomputed from scratch. + * true if some methods have wide forward jumps using ASM pseudo + * instructions, which need to be expanded into sequences of standard + * bytecode instructions. In this case the class is re-read and re-written + * with a ClassReader -> ClassWriter chain to perform this transformation. */ - private boolean computeFrames; - - /** - * true if the stack map tables of this class are invalid. The - * {@link MethodWriter#resizeInstructions} method cannot transform existing - * stack map tables, and so produces potentially invalid classes when it is - * executed. In this case the class is reread and rewritten with the - * {@link #COMPUTE_FRAMES} option (the resizeInstructions method can resize - * stack map tables when this option is used). - */ - boolean invalidFrames; + boolean hasAsmInsns; // ------------------------------------------------------------------------ // Static initializer @@ -517,7 +527,7 @@ public class ClassWriter extends ClassVisitor { String s = "AAAAAAAAAAAAAAAABCLMMDDDDDEEEEEEEEEEEEEEEEEEEEAAAAAAAADD" + "DDDEEEEEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAANAAAAAAAAAAAAAAAAAAAAJJJJJJJJJJJJJJJJDOPAA" - + "AAAAGGGGGGGHIFBFAAFFAARQJJKKJJJJJJJJJJJJJJJJJJ"; + + "AAAAGGGGGGGHIFBFAAFFAARQJJKKSSSSSSSSSSSSSSSSSS"; for (i = 0; i < b.length; ++i) { b[i] = (byte) (s.charAt(i) - 'A'); } @@ -571,7 +581,7 @@ public class ClassWriter extends ClassVisitor { // // temporary opcodes used internally by ASM - see Label and // MethodWriter // for (i = 202; i < 220; ++i) { - // b[i] = LABEL_INSN; + // b[i] = ASM_LABEL_INSN; // } // // // LDC(_W) instructions @@ -614,8 +624,9 @@ public class ClassWriter extends ClassVisitor { key2 = new Item(); key3 = new Item(); key4 = new Item(); - this.computeMaxs = (flags & COMPUTE_MAXS) != 0; - this.computeFrames = (flags & COMPUTE_FRAMES) != 0; + this.compute = (flags & COMPUTE_FRAMES) != 0 ? MethodWriter.FRAMES + : ((flags & COMPUTE_MAXS) != 0 ? MethodWriter.MAXS + : MethodWriter.NOTHING); } /** @@ -645,9 +656,9 @@ public class ClassWriter extends ClassVisitor { * @param flags * option flags that can be used to modify the default behavior * of this class. These option flags do not affect methods - * that are copied as is in the new class. This means that the - * maximum stack size nor the stack frames will be computed for - * these methods. See {@link #COMPUTE_MAXS}, + * that are copied as is in the new class. This means that + * neither the maximum stack size nor the stack frames will be + * computed for these methods. See {@link #COMPUTE_MAXS}, * {@link #COMPUTE_FRAMES}. */ public ClassWriter(final ClassReader classReader, final int flags) { @@ -791,7 +802,7 @@ public class ClassWriter extends ClassVisitor { public final MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { return new MethodWriter(this, access, name, desc, signature, - exceptions, computeMaxs, computeFrames); + exceptions, compute); } @Override @@ -977,22 +988,20 @@ public class ClassWriter extends ClassVisitor { if (attrs != null) { attrs.put(this, null, 0, -1, -1, out); } - if (invalidFrames) { + if (hasAsmInsns) { anns = null; ianns = null; attrs = null; innerClassesCount = 0; innerClasses = null; - bootstrapMethodsCount = 0; - bootstrapMethods = null; firstField = null; lastField = null; firstMethod = null; lastMethod = null; - computeMaxs = false; - computeFrames = true; - invalidFrames = false; - new ClassReader(out.data).accept(this, ClassReader.SKIP_FRAMES); + compute = MethodWriter.INSERTED_FRAMES; + hasAsmInsns = false; + new ClassReader(out.data).accept(this, ClassReader.EXPAND_FRAMES + | ClassReader.EXPAND_ASM_INSNS); return toByteArray(); } return out.data; diff --git a/spring-core/src/main/java/org/springframework/asm/CurrentFrame.java b/spring-core/src/main/java/org/springframework/asm/CurrentFrame.java new file mode 100644 index 0000000000..715014bc6c --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/CurrentFrame.java @@ -0,0 +1,56 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.springframework.asm; + +/** + * Information about the input stack map frame at the "current" instruction of a + * method. This is implemented as a Frame subclass for a "basic block" + * containing only one instruction. + * + * @author Eric Bruneton + */ +class CurrentFrame extends Frame { + + /** + * Sets this CurrentFrame to the input stack map frame of the next "current" + * instruction, i.e. the instruction just after the given one. It is assumed + * that the value of this object when this method is called is the stack map + * frame status just before the given instruction is executed. + */ + @Override + void execute(int opcode, int arg, ClassWriter cw, Item item) { + super.execute(opcode, arg, cw, item); + Frame successor = new Frame(); + merge(cw, successor, 0); + set(successor); + owner.inputStackTop = 0; + } +} diff --git a/spring-core/src/main/java/org/springframework/asm/Frame.java b/spring-core/src/main/java/org/springframework/asm/Frame.java index 389e416ba0..30c7f0d588 100644 --- a/spring-core/src/main/java/org/springframework/asm/Frame.java +++ b/spring-core/src/main/java/org/springframework/asm/Frame.java @@ -34,7 +34,7 @@ package org.springframework.asm; * * @author Eric Bruneton */ -final class Frame { +class Frame { /* * Frames are computed in a two steps process: during the visit of each @@ -496,7 +496,7 @@ final class Frame { * When the stack map frames are completely computed, this field is the * actual number of types in {@link #outputStack}. */ - private int outputStackTop; + int outputStackTop; /** * Number of types that are initialized in the basic block. @@ -520,6 +520,110 @@ final class Frame { */ private int[] initializations; + /** + * Sets this frame to the given value. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param nLocal + * the number of local variables. + * @param local + * the local variable types. Primitive types are represented by + * {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, + * {@link Opcodes#FLOAT}, {@link Opcodes#LONG}, + * {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or + * {@link Opcodes#UNINITIALIZED_THIS} (long and double are + * represented by a single element). Reference types are + * represented by String objects (representing internal names), + * and uninitialized types by Label objects (this label + * designates the NEW instruction that created this uninitialized + * value). + * @param nStack + * the number of operand stack elements. + * @param stack + * the operand stack types (same format as the "local" array). + */ + final void set(ClassWriter cw, final int nLocal, final Object[] local, + final int nStack, final Object[] stack) { + int i = convert(cw, nLocal, local, inputLocals); + while (i < local.length) { + inputLocals[i++] = TOP; + } + int nStackTop = 0; + for (int j = 0; j < nStack; ++j) { + if (stack[j] == Opcodes.LONG || stack[j] == Opcodes.DOUBLE) { + ++nStackTop; + } + } + inputStack = new int[nStack + nStackTop]; + convert(cw, nStack, stack, inputStack); + outputStackTop = 0; + initializationCount = 0; + } + + /** + * Converts types from the MethodWriter.visitFrame() format to the Frame + * format. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param nInput + * the number of types to convert. + * @param input + * the types to convert. Primitive types are represented by + * {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, + * {@link Opcodes#FLOAT}, {@link Opcodes#LONG}, + * {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or + * {@link Opcodes#UNINITIALIZED_THIS} (long and double are + * represented by a single element). Reference types are + * represented by String objects (representing internal names), + * and uninitialized types by Label objects (this label + * designates the NEW instruction that created this uninitialized + * value). + * @param output + * where to store the converted types. + * @return the number of output elements. + */ + private static int convert(ClassWriter cw, int nInput, Object[] input, + int[] output) { + int i = 0; + for (int j = 0; j < nInput; ++j) { + if (input[j] instanceof Integer) { + output[i++] = BASE | ((Integer) input[j]).intValue(); + if (input[j] == Opcodes.LONG || input[j] == Opcodes.DOUBLE) { + output[i++] = TOP; + } + } else if (input[j] instanceof String) { + output[i++] = type(cw, Type.getObjectType((String) input[j]) + .getDescriptor()); + } else { + output[i++] = UNINITIALIZED + | cw.addUninitializedType("", + ((Label) input[j]).position); + } + } + return i; + } + + /** + * Sets this frame to the value of the given frame. WARNING: after this + * method is called the two frames share the same data structures. It is + * recommended to discard the given frame f to avoid unexpected side + * effects. + * + * @param f + * The new frame value. + */ + final void set(final Frame f) { + inputLocals = f.inputLocals; + inputStack = f.inputStack; + outputLocals = f.outputLocals; + outputStack = f.outputStack; + outputStackTop = f.outputStackTop; + initializationCount = f.initializationCount; + initializations = f.initializations; + } + /** * Returns the output frame local variable type at the given index. * @@ -585,7 +689,7 @@ final class Frame { } // pushes the type on the output stack outputStack[outputStackTop++] = type; - // updates the maximun height reached by the output stack, if needed + // updates the maximum height reached by the output stack, if needed int top = owner.inputStackTop + outputStackTop; if (top > owner.outputStackMax) { owner.outputStackMax = top; @@ -809,7 +913,7 @@ final class Frame { * @param maxLocals * the maximum number of local variables of this method. */ - void initInputFrame(final ClassWriter cw, final int access, + final void initInputFrame(final ClassWriter cw, final int access, final Type[] args, final int maxLocals) { inputLocals = new int[maxLocals]; inputStack = new int[0]; @@ -1283,7 +1387,7 @@ final class Frame { * @return true if the input frame of the given label has been * changed by this operation. */ - boolean merge(final ClassWriter cw, final Frame frame, final int edge) { + final boolean merge(final ClassWriter cw, final Frame frame, final int edge) { boolean changed = false; int i, s, dim, kind, t; diff --git a/spring-core/src/main/java/org/springframework/asm/Item.java b/spring-core/src/main/java/org/springframework/asm/Item.java index 91dc701d09..7bebf34946 100644 --- a/spring-core/src/main/java/org/springframework/asm/Item.java +++ b/spring-core/src/main/java/org/springframework/asm/Item.java @@ -201,6 +201,7 @@ final class Item { * @param strVal3 * third part of the value of this item. */ + @SuppressWarnings("fallthrough") void set(final int type, final String strVal1, final String strVal2, final String strVal3) { this.type = type; @@ -210,8 +211,6 @@ final class Item { switch (type) { case ClassWriter.CLASS: this.intVal = 0; // intVal of a class must be zero, see visitInnerClass - hashCode = 0x7FFFFFFF & (type + strVal1.hashCode()); - return; case ClassWriter.UTF8: case ClassWriter.STR: case ClassWriter.MTYPE: diff --git a/spring-core/src/main/java/org/springframework/asm/Label.java b/spring-core/src/main/java/org/springframework/asm/Label.java index deb84a0941..eb25824c82 100644 --- a/spring-core/src/main/java/org/springframework/asm/Label.java +++ b/spring-core/src/main/java/org/springframework/asm/Label.java @@ -364,9 +364,8 @@ public class Label { * small to store the offset. In such a case the corresponding jump * instruction is replaced with a pseudo instruction (using unused * opcodes) using an unsigned two bytes offset. These pseudo - * instructions will need to be replaced with true instructions with - * wider offsets (4 bytes instead of 2). This is done in - * {@link MethodWriter#resizeInstructions}. + * instructions will be replaced with standard bytecode instructions + * with wider offsets (4 bytes instead of 2), in ClassReader. * @throws IllegalArgumentException * if this label has already been resolved, or if it has not * been created by the given code writer. diff --git a/spring-core/src/main/java/org/springframework/asm/MethodVisitor.java b/spring-core/src/main/java/org/springframework/asm/MethodVisitor.java index 4d5c50b98e..d512aed688 100644 --- a/spring-core/src/main/java/org/springframework/asm/MethodVisitor.java +++ b/spring-core/src/main/java/org/springframework/asm/MethodVisitor.java @@ -33,15 +33,16 @@ package org.springframework.asm; * A visitor to visit a Java method. The methods of this class must be called in * the following order: ( visitParameter )* [ * visitAnnotationDefault ] ( visitAnnotation | - * visitTypeAnnotation | visitAttribute )* [ - * visitCode ( visitFrame | visitXInsn | - * visitLabel | visitInsnAnnotation | - * visitTryCatchBlock | visitTryCatchBlockAnnotation | - * visitLocalVariable | visitLocalVariableAnnotation | - * visitLineNumber )* visitMaxs ] visitEnd. In - * addition, the visitXInsn and visitLabel methods must - * be called in the sequential order of the bytecode instructions of the visited - * code, visitInsnAnnotation must be called after the annotated + * visitParameterAnnotation visitTypeAnnotation | + * visitAttribute )* [ visitCode ( visitFrame | + * visitXInsn | visitLabel | + * visitInsnAnnotation | visitTryCatchBlock | + * visitTryCatchAnnotation | visitLocalVariable | + * visitLocalVariableAnnotation | visitLineNumber )* + * visitMaxs ] visitEnd. In addition, the + * visitXInsn and visitLabel methods must be called in + * the sequential order of the bytecode instructions of the visited code, + * visitInsnAnnotation must be called after the annotated * instruction, visitTryCatchBlock must be called before the * labels passed as arguments have been visited, * visitTryCatchBlockAnnotation must be called after the diff --git a/spring-core/src/main/java/org/springframework/asm/MethodWriter.java b/spring-core/src/main/java/org/springframework/asm/MethodWriter.java index e8b226d230..c26e41665a 100644 --- a/spring-core/src/main/java/org/springframework/asm/MethodWriter.java +++ b/spring-core/src/main/java/org/springframework/asm/MethodWriter.java @@ -99,7 +99,19 @@ class MethodWriter extends MethodVisitor { * * @see #compute */ - private static final int FRAMES = 0; + static final int FRAMES = 0; + + /** + * Indicates that the stack map frames of type F_INSERT must be computed. + * The other frames are not (re)computed. They should all be of type F_NEW + * and should be sufficient to compute the content of the F_INSERT frames, + * together with the bytecode instructions between a F_NEW and a F_INSERT + * frame - and without any knowledge of the type hierarchy (by definition of + * F_INSERT). + * + * @see #compute + */ + static final int INSERTED_FRAMES = 1; /** * Indicates that the maximum stack size and number of local variables must @@ -107,14 +119,14 @@ class MethodWriter extends MethodVisitor { * * @see #compute */ - private static final int MAXS = 1; + static final int MAXS = 2; /** * Indicates that nothing must be automatically computed. * * @see #compute */ - private static final int NOTHING = 2; + static final int NOTHING = 3; /** * The class writer to which this method must be added. @@ -354,11 +366,6 @@ class MethodWriter extends MethodVisitor { */ private Attribute cattrs; - /** - * Indicates if some jump instructions are too small and need to be resized. - */ - private boolean resize; - /** * The number of subroutines in this method. */ @@ -380,6 +387,7 @@ class MethodWriter extends MethodVisitor { * Indicates what must be automatically computed. * * @see #FRAMES + * @see #INSERTED_FRAMES * @see #MAXS * @see #NOTHING */ @@ -442,17 +450,12 @@ class MethodWriter extends MethodVisitor { * @param exceptions * the internal names of the method's exceptions. May be * null. - * @param computeMaxs - * true if the maximum stack size and number of local - * variables must be automatically computed. - * @param computeFrames - * true if the stack map tables must be recomputed from - * scratch. + * @param compute + * Indicates what must be automatically computed (see #compute). */ MethodWriter(final ClassWriter cw, final int access, final String name, final String desc, final String signature, - final String[] exceptions, final boolean computeMaxs, - final boolean computeFrames) { + final String[] exceptions, final int compute) { super(Opcodes.ASM5); if (cw.firstMethod == null) { cw.firstMethod = this; @@ -478,8 +481,8 @@ class MethodWriter extends MethodVisitor { this.exceptions[i] = cw.newClass(exceptions[i]); } } - this.compute = computeFrames ? FRAMES : (computeMaxs ? MAXS : NOTHING); - if (computeMaxs || computeFrames) { + this.compute = compute; + if (compute != NOTHING) { // updates maxLocals int size = Type.getArgumentsAndReturnSizes(descriptor) >> 2; if ((access & Opcodes.ACC_STATIC) != 0) { @@ -614,7 +617,29 @@ class MethodWriter extends MethodVisitor { return; } - if (type == Opcodes.F_NEW) { + if (compute == INSERTED_FRAMES) { + if (currentBlock.frame == null) { + // This should happen only once, for the implicit first frame + // (which is explicitly visited in ClassReader if the + // EXPAND_ASM_INSNS option is used). + currentBlock.frame = new CurrentFrame(); + currentBlock.frame.owner = currentBlock; + currentBlock.frame.initInputFrame(cw, access, + Type.getArgumentTypes(descriptor), nLocal); + visitImplicitFirstFrame(); + } else { + if (type == Opcodes.F_NEW) { + currentBlock.frame.set(cw, nLocal, local, nStack, stack); + } else { + // In this case type is equal to F_INSERT by hypothesis, and + // currentBlock.frame contains the stack map frame at the + // current instruction, computed from the last F_NEW frame + // and the bytecode instructions in between (via calls to + // CurrentFrame#execute). + } + visitFrame(currentBlock.frame); + } + } else if (type == Opcodes.F_NEW) { if (previousFrame == null) { visitImplicitFirstFrame(); } @@ -718,7 +743,7 @@ class MethodWriter extends MethodVisitor { // update currentBlock // Label currentBlock = this.currentBlock; if (currentBlock != null) { - if (compute == FRAMES) { + if (compute == FRAMES || compute == INSERTED_FRAMES) { currentBlock.frame.execute(opcode, 0, null, null); } else { // updates current and max stack sizes @@ -741,7 +766,7 @@ class MethodWriter extends MethodVisitor { lastCodeOffset = code.length; // Label currentBlock = this.currentBlock; if (currentBlock != null) { - if (compute == FRAMES) { + if (compute == FRAMES || compute == INSERTED_FRAMES) { currentBlock.frame.execute(opcode, operand, null, null); } else if (opcode != Opcodes.NEWARRAY) { // updates current and max stack sizes only for NEWARRAY @@ -766,7 +791,7 @@ class MethodWriter extends MethodVisitor { lastCodeOffset = code.length; // Label currentBlock = this.currentBlock; if (currentBlock != null) { - if (compute == FRAMES) { + if (compute == FRAMES || compute == INSERTED_FRAMES) { currentBlock.frame.execute(opcode, var, null, null); } else { // updates current and max stack sizes @@ -826,7 +851,7 @@ class MethodWriter extends MethodVisitor { Item i = cw.newClassItem(type); // Label currentBlock = this.currentBlock; if (currentBlock != null) { - if (compute == FRAMES) { + if (compute == FRAMES || compute == INSERTED_FRAMES) { currentBlock.frame.execute(opcode, code.length, cw, i); } else if (opcode == Opcodes.NEW) { // updates current and max stack sizes only if opcode == NEW @@ -849,7 +874,7 @@ class MethodWriter extends MethodVisitor { Item i = cw.newFieldItem(owner, name, desc); // Label currentBlock = this.currentBlock; if (currentBlock != null) { - if (compute == FRAMES) { + if (compute == FRAMES || compute == INSERTED_FRAMES) { currentBlock.frame.execute(opcode, 0, cw, i); } else { int size; @@ -889,7 +914,7 @@ class MethodWriter extends MethodVisitor { int argSize = i.intVal; // Label currentBlock = this.currentBlock; if (currentBlock != null) { - if (compute == FRAMES) { + if (compute == FRAMES || compute == INSERTED_FRAMES) { currentBlock.frame.execute(opcode, 0, cw, i); } else { /* @@ -941,7 +966,7 @@ class MethodWriter extends MethodVisitor { int argSize = i.intVal; // Label currentBlock = this.currentBlock; if (currentBlock != null) { - if (compute == FRAMES) { + if (compute == FRAMES || compute == INSERTED_FRAMES) { currentBlock.frame.execute(Opcodes.INVOKEDYNAMIC, 0, cw, i); } else { /* @@ -975,7 +1000,9 @@ class MethodWriter extends MethodVisitor { } @Override - public void visitJumpInsn(final int opcode, final Label label) { + public void visitJumpInsn(int opcode, final Label label) { + boolean isWide = opcode >= 200; // GOTO_W + opcode = isWide ? opcode - 33 : opcode; lastCodeOffset = code.length; Label nextInsn = null; // Label currentBlock = this.currentBlock; @@ -990,6 +1017,8 @@ class MethodWriter extends MethodVisitor { // creates a Label for the next basic block nextInsn = new Label(); } + } else if (compute == INSERTED_FRAMES) { + currentBlock.frame.execute(opcode, 0, null, null); } else { if (opcode == Opcodes.JSR) { if ((label.status & Label.SUBROUTINE) == 0) { @@ -1041,6 +1070,14 @@ class MethodWriter extends MethodVisitor { code.putByte(200); // GOTO_W } label.put(this, code, code.length - 1, true); + } else if (isWide) { + /* + * case of a GOTO_W or JSR_W specified by the user (normally + * ClassReader when used to resize instructions). In this case we + * keep the original instruction. + */ + code.putByte(opcode + 33); + label.put(this, code, code.length - 1, true); } else { /* * case of a backward jump with an offset >= -32768, or of a forward @@ -1068,7 +1105,7 @@ class MethodWriter extends MethodVisitor { @Override public void visitLabel(final Label label) { // resolves previous forward references to label, if any - resize |= label.resolve(this, code.length, code.data); + cw.hasAsmInsns |= label.resolve(this, code.length, code.data); // updates currentBlock if ((label.status & Label.DEBUG) != 0) { return; @@ -1101,6 +1138,18 @@ class MethodWriter extends MethodVisitor { previousBlock.successor = label; } previousBlock = label; + } else if (compute == INSERTED_FRAMES) { + if (currentBlock == null) { + // This case should happen only once, for the visitLabel call in + // the constructor. Indeed, if compute is equal to + // INSERTED_FRAMES currentBlock can not be set back to null (see + // #noSuccessor). + currentBlock = label; + } else { + // Updates the frame owner so that a correct frame offset is + // computed in visitFrame(Frame). + currentBlock.frame.owner = label; + } } else if (compute == MAXS) { if (currentBlock != null) { // ends current block (with one new successor) @@ -1126,7 +1175,7 @@ class MethodWriter extends MethodVisitor { Item i = cw.newConstItem(cst); // Label currentBlock = this.currentBlock; if (currentBlock != null) { - if (compute == FRAMES) { + if (compute == FRAMES || compute == INSERTED_FRAMES) { currentBlock.frame.execute(Opcodes.LDC, 0, cw, i); } else { int size; @@ -1158,7 +1207,7 @@ class MethodWriter extends MethodVisitor { public void visitIincInsn(final int var, final int increment) { lastCodeOffset = code.length; if (currentBlock != null) { - if (compute == FRAMES) { + if (compute == FRAMES || compute == INSERTED_FRAMES) { currentBlock.frame.execute(Opcodes.IINC, var, null, null); } } @@ -1245,7 +1294,7 @@ class MethodWriter extends MethodVisitor { Item i = cw.newClassItem(desc); // Label currentBlock = this.currentBlock; if (currentBlock != null) { - if (compute == FRAMES) { + if (compute == FRAMES || compute == INSERTED_FRAMES) { currentBlock.frame.execute(Opcodes.MULTIANEWARRAY, dims, cw, i); } else { // updates current stack size (max stack size unchanged because @@ -1401,14 +1450,6 @@ class MethodWriter extends MethodVisitor { @Override public void visitMaxs(final int maxStack, final int maxLocals) { - if (resize) { - // replaces the temporary jump opcodes introduced by Label.resolve. - if (ClassReader.RESIZE) { - resizeInstructions(); - } else { - throw new RuntimeException("Method code too large!"); - } - } if (ClassReader.FRAMES && compute == FRAMES) { // completes the control flow graph with exception handler blocks Handler handler = firstHandler; @@ -1439,8 +1480,8 @@ class MethodWriter extends MethodVisitor { // creates and visits the first (implicit) frame Frame f = labels.frame; - Type[] args = Type.getArgumentTypes(descriptor); - f.initInputFrame(cw, access, args, this.maxLocals); + f.initInputFrame(cw, access, Type.getArgumentTypes(descriptor), + this.maxLocals); visitFrame(f); /* @@ -1688,7 +1729,9 @@ class MethodWriter extends MethodVisitor { } else { currentBlock.outputStackMax = maxStackSize; } - currentBlock = null; + if (compute != INSERTED_FRAMES) { + currentBlock = null; + } } // ------------------------------------------------------------------------ @@ -2347,569 +2390,4 @@ class MethodWriter extends MethodVisitor { attrs.put(cw, null, 0, -1, -1, out); } } - - // ------------------------------------------------------------------------ - // Utility methods: instruction resizing (used to handle GOTO_W and JSR_W) - // ------------------------------------------------------------------------ - - /** - * Resizes and replaces the temporary instructions inserted by - * {@link Label#resolve} for wide forward jumps, while keeping jump offsets - * and instruction addresses consistent. This may require to resize other - * existing instructions, or even to introduce new instructions: for - * example, increasing the size of an instruction by 2 at the middle of a - * method can increases the offset of an IFEQ instruction from 32766 to - * 32768, in which case IFEQ 32766 must be replaced with IFNEQ 8 GOTO_W - * 32765. This, in turn, may require to increase the size of another jump - * instruction, and so on... All these operations are handled automatically - * by this method. - *

- * This method must be called after all the method that is being built - * has been visited. In particular, the {@link Label Label} objects used - * to construct the method are no longer valid after this method has been - * called. - */ - private void resizeInstructions() { - byte[] b = code.data; // bytecode of the method - int u, v, label; // indexes in b - int i, j; // loop indexes - /* - * 1st step: As explained above, resizing an instruction may require to - * resize another one, which may require to resize yet another one, and - * so on. The first step of the algorithm consists in finding all the - * instructions that need to be resized, without modifying the code. - * This is done by the following "fix point" algorithm: - * - * Parse the code to find the jump instructions whose offset will need - * more than 2 bytes to be stored (the future offset is computed from - * the current offset and from the number of bytes that will be inserted - * or removed between the source and target instructions). For each such - * instruction, adds an entry in (a copy of) the indexes and sizes - * arrays (if this has not already been done in a previous iteration!). - * - * If at least one entry has been added during the previous step, go - * back to the beginning, otherwise stop. - * - * In fact the real algorithm is complicated by the fact that the size - * of TABLESWITCH and LOOKUPSWITCH instructions depends on their - * position in the bytecode (because of padding). In order to ensure the - * convergence of the algorithm, the number of bytes to be added or - * removed from these instructions is over estimated during the previous - * loop, and computed exactly only after the loop is finished (this - * requires another pass to parse the bytecode of the method). - */ - int[] allIndexes = new int[0]; // copy of indexes - int[] allSizes = new int[0]; // copy of sizes - boolean[] resize; // instructions to be resized - int newOffset; // future offset of a jump instruction - - resize = new boolean[code.length]; - - // 3 = loop again, 2 = loop ended, 1 = last pass, 0 = done - int state = 3; - do { - if (state == 3) { - state = 2; - } - u = 0; - while (u < b.length) { - int opcode = b[u] & 0xFF; // opcode of current instruction - int insert = 0; // bytes to be added after this instruction - - switch (ClassWriter.TYPE[opcode]) { - case ClassWriter.NOARG_INSN: - case ClassWriter.IMPLVAR_INSN: - u += 1; - break; - case ClassWriter.LABEL_INSN: - if (opcode > 201) { - // converts temporary opcodes 202 to 217, 218 and - // 219 to IFEQ ... JSR (inclusive), IFNULL and - // IFNONNULL - opcode = opcode < 218 ? opcode - 49 : opcode - 20; - label = u + readUnsignedShort(b, u + 1); - } else { - label = u + readShort(b, u + 1); - } - newOffset = getNewOffset(allIndexes, allSizes, u, label); - if (newOffset < Short.MIN_VALUE - || newOffset > Short.MAX_VALUE) { - if (!resize[u]) { - if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) { - // two additional bytes will be required to - // replace this GOTO or JSR instruction with - // a GOTO_W or a JSR_W - insert = 2; - } else { - // five additional bytes will be required to - // replace this IFxxx instruction with - // IFNOTxxx GOTO_W , where IFNOTxxx - // is the "opposite" opcode of IFxxx (i.e., - // IFNE for IFEQ) and where designates - // the instruction just after the GOTO_W. - insert = 5; - } - resize[u] = true; - } - } - u += 3; - break; - case ClassWriter.LABELW_INSN: - u += 5; - break; - case ClassWriter.TABL_INSN: - if (state == 1) { - // true number of bytes to be added (or removed) - // from this instruction = (future number of padding - // bytes - current number of padding byte) - - // previously over estimated variation = - // = ((3 - newOffset%4) - (3 - u%4)) - u%4 - // = (-newOffset%4 + u%4) - u%4 - // = -(newOffset & 3) - newOffset = getNewOffset(allIndexes, allSizes, 0, u); - insert = -(newOffset & 3); - } else if (!resize[u]) { - // over estimation of the number of bytes to be - // added to this instruction = 3 - current number - // of padding bytes = 3 - (3 - u%4) = u%4 = u & 3 - insert = u & 3; - resize[u] = true; - } - // skips instruction - u = u + 4 - (u & 3); - u += 4 * (readInt(b, u + 8) - readInt(b, u + 4) + 1) + 12; - break; - case ClassWriter.LOOK_INSN: - if (state == 1) { - // like TABL_INSN - newOffset = getNewOffset(allIndexes, allSizes, 0, u); - insert = -(newOffset & 3); - } else if (!resize[u]) { - // like TABL_INSN - insert = u & 3; - resize[u] = true; - } - // skips instruction - u = u + 4 - (u & 3); - u += 8 * readInt(b, u + 4) + 8; - break; - case ClassWriter.WIDE_INSN: - opcode = b[u + 1] & 0xFF; - if (opcode == Opcodes.IINC) { - u += 6; - } else { - u += 4; - } - break; - case ClassWriter.VAR_INSN: - case ClassWriter.SBYTE_INSN: - case ClassWriter.LDC_INSN: - u += 2; - break; - case ClassWriter.SHORT_INSN: - case ClassWriter.LDCW_INSN: - case ClassWriter.FIELDORMETH_INSN: - case ClassWriter.TYPE_INSN: - case ClassWriter.IINC_INSN: - u += 3; - break; - case ClassWriter.ITFMETH_INSN: - case ClassWriter.INDYMETH_INSN: - u += 5; - break; - // case ClassWriter.MANA_INSN: - default: - u += 4; - break; - } - if (insert != 0) { - // adds a new (u, insert) entry in the allIndexes and - // allSizes arrays - int[] newIndexes = new int[allIndexes.length + 1]; - int[] newSizes = new int[allSizes.length + 1]; - System.arraycopy(allIndexes, 0, newIndexes, 0, - allIndexes.length); - System.arraycopy(allSizes, 0, newSizes, 0, allSizes.length); - newIndexes[allIndexes.length] = u; - newSizes[allSizes.length] = insert; - allIndexes = newIndexes; - allSizes = newSizes; - if (insert > 0) { - state = 3; - } - } - } - if (state < 3) { - --state; - } - } while (state != 0); - - // 2nd step: - // copies the bytecode of the method into a new bytevector, updates the - // offsets, and inserts (or removes) bytes as requested. - - ByteVector newCode = new ByteVector(code.length); - - u = 0; - while (u < code.length) { - int opcode = b[u] & 0xFF; - switch (ClassWriter.TYPE[opcode]) { - case ClassWriter.NOARG_INSN: - case ClassWriter.IMPLVAR_INSN: - newCode.putByte(opcode); - u += 1; - break; - case ClassWriter.LABEL_INSN: - if (opcode > 201) { - // changes temporary opcodes 202 to 217 (inclusive), 218 - // and 219 to IFEQ ... JSR (inclusive), IFNULL and - // IFNONNULL - opcode = opcode < 218 ? opcode - 49 : opcode - 20; - label = u + readUnsignedShort(b, u + 1); - } else { - label = u + readShort(b, u + 1); - } - newOffset = getNewOffset(allIndexes, allSizes, u, label); - if (resize[u]) { - // replaces GOTO with GOTO_W, JSR with JSR_W and IFxxx - // with IFNOTxxx GOTO_W , where IFNOTxxx is - // the "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) - // and where designates the instruction just after - // the GOTO_W. - if (opcode == Opcodes.GOTO) { - newCode.putByte(200); // GOTO_W - } else if (opcode == Opcodes.JSR) { - newCode.putByte(201); // JSR_W - } else { - newCode.putByte(opcode <= 166 ? ((opcode + 1) ^ 1) - 1 - : opcode ^ 1); - newCode.putShort(8); // jump offset - newCode.putByte(200); // GOTO_W - // newOffset now computed from start of GOTO_W - newOffset -= 3; - } - newCode.putInt(newOffset); - } else { - newCode.putByte(opcode); - newCode.putShort(newOffset); - } - u += 3; - break; - case ClassWriter.LABELW_INSN: - label = u + readInt(b, u + 1); - newOffset = getNewOffset(allIndexes, allSizes, u, label); - newCode.putByte(opcode); - newCode.putInt(newOffset); - u += 5; - break; - case ClassWriter.TABL_INSN: - // skips 0 to 3 padding bytes - v = u; - u = u + 4 - (v & 3); - // reads and copies instruction - newCode.putByte(Opcodes.TABLESWITCH); - newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4); - label = v + readInt(b, u); - u += 4; - newOffset = getNewOffset(allIndexes, allSizes, v, label); - newCode.putInt(newOffset); - j = readInt(b, u); - u += 4; - newCode.putInt(j); - j = readInt(b, u) - j + 1; - u += 4; - newCode.putInt(readInt(b, u - 4)); - for (; j > 0; --j) { - label = v + readInt(b, u); - u += 4; - newOffset = getNewOffset(allIndexes, allSizes, v, label); - newCode.putInt(newOffset); - } - break; - case ClassWriter.LOOK_INSN: - // skips 0 to 3 padding bytes - v = u; - u = u + 4 - (v & 3); - // reads and copies instruction - newCode.putByte(Opcodes.LOOKUPSWITCH); - newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4); - label = v + readInt(b, u); - u += 4; - newOffset = getNewOffset(allIndexes, allSizes, v, label); - newCode.putInt(newOffset); - j = readInt(b, u); - u += 4; - newCode.putInt(j); - for (; j > 0; --j) { - newCode.putInt(readInt(b, u)); - u += 4; - label = v + readInt(b, u); - u += 4; - newOffset = getNewOffset(allIndexes, allSizes, v, label); - newCode.putInt(newOffset); - } - break; - case ClassWriter.WIDE_INSN: - opcode = b[u + 1] & 0xFF; - if (opcode == Opcodes.IINC) { - newCode.putByteArray(b, u, 6); - u += 6; - } else { - newCode.putByteArray(b, u, 4); - u += 4; - } - break; - case ClassWriter.VAR_INSN: - case ClassWriter.SBYTE_INSN: - case ClassWriter.LDC_INSN: - newCode.putByteArray(b, u, 2); - u += 2; - break; - case ClassWriter.SHORT_INSN: - case ClassWriter.LDCW_INSN: - case ClassWriter.FIELDORMETH_INSN: - case ClassWriter.TYPE_INSN: - case ClassWriter.IINC_INSN: - newCode.putByteArray(b, u, 3); - u += 3; - break; - case ClassWriter.ITFMETH_INSN: - case ClassWriter.INDYMETH_INSN: - newCode.putByteArray(b, u, 5); - u += 5; - break; - // case MANA_INSN: - default: - newCode.putByteArray(b, u, 4); - u += 4; - break; - } - } - - // updates the stack map frame labels - if (compute == FRAMES) { - Label l = labels; - while (l != null) { - /* - * Detects the labels that are just after an IF instruction that - * has been resized with the IFNOT GOTO_W pattern. These labels - * are now the target of a jump instruction (the IFNOT - * instruction). Note that we need the original label position - * here. getNewOffset must therefore never have been called for - * this label. - */ - u = l.position - 3; - if (u >= 0 && resize[u]) { - l.status |= Label.TARGET; - } - getNewOffset(allIndexes, allSizes, l); - l = l.successor; - } - // Update the offsets in the uninitialized types - if (cw.typeTable != null) { - for (i = 0; i < cw.typeTable.length; ++i) { - Item item = cw.typeTable[i]; - if (item != null && item.type == ClassWriter.TYPE_UNINIT) { - item.intVal = getNewOffset(allIndexes, allSizes, 0, - item.intVal); - } - } - } - // The stack map frames are not serialized yet, so we don't need - // to update them. They will be serialized in visitMaxs. - } else if (frameCount > 0) { - /* - * Resizing an existing stack map frame table is really hard. Not - * only the table must be parsed to update the offets, but new - * frames may be needed for jump instructions that were inserted by - * this method. And updating the offsets or inserting frames can - * change the format of the following frames, in case of packed - * frames. In practice the whole table must be recomputed. For this - * the frames are marked as potentially invalid. This will cause the - * whole class to be reread and rewritten with the COMPUTE_FRAMES - * option (see the ClassWriter.toByteArray method). This is not very - * efficient but is much easier and requires much less code than any - * other method I can think of. - */ - cw.invalidFrames = true; - } - // updates the exception handler block labels - Handler h = firstHandler; - while (h != null) { - getNewOffset(allIndexes, allSizes, h.start); - getNewOffset(allIndexes, allSizes, h.end); - getNewOffset(allIndexes, allSizes, h.handler); - h = h.next; - } - // updates the instructions addresses in the - // local var and line number tables - for (i = 0; i < 2; ++i) { - ByteVector bv = i == 0 ? localVar : localVarType; - if (bv != null) { - b = bv.data; - u = 0; - while (u < bv.length) { - label = readUnsignedShort(b, u); - newOffset = getNewOffset(allIndexes, allSizes, 0, label); - writeShort(b, u, newOffset); - label += readUnsignedShort(b, u + 2); - newOffset = getNewOffset(allIndexes, allSizes, 0, label) - - newOffset; - writeShort(b, u + 2, newOffset); - u += 10; - } - } - } - if (lineNumber != null) { - b = lineNumber.data; - u = 0; - while (u < lineNumber.length) { - writeShort( - b, - u, - getNewOffset(allIndexes, allSizes, 0, - readUnsignedShort(b, u))); - u += 4; - } - } - // updates the labels of the other attributes - Attribute attr = cattrs; - while (attr != null) { - Label[] labels = attr.getLabels(); - if (labels != null) { - for (i = labels.length - 1; i >= 0; --i) { - getNewOffset(allIndexes, allSizes, labels[i]); - } - } - attr = attr.next; - } - - // replaces old bytecodes with new ones - code = newCode; - } - - /** - * Reads an unsigned short value in the given byte array. - * - * @param b - * a byte array. - * @param index - * the start index of the value to be read. - * @return the read value. - */ - static int readUnsignedShort(final byte[] b, final int index) { - return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF); - } - - /** - * Reads a signed short value in the given byte array. - * - * @param b - * a byte array. - * @param index - * the start index of the value to be read. - * @return the read value. - */ - static short readShort(final byte[] b, final int index) { - return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF)); - } - - /** - * Reads a signed int value in the given byte array. - * - * @param b - * a byte array. - * @param index - * the start index of the value to be read. - * @return the read value. - */ - static int readInt(final byte[] b, final int index) { - return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16) - | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF); - } - - /** - * Writes a short value in the given byte array. - * - * @param b - * a byte array. - * @param index - * where the first byte of the short value must be written. - * @param s - * the value to be written in the given byte array. - */ - static void writeShort(final byte[] b, final int index, final int s) { - b[index] = (byte) (s >>> 8); - b[index + 1] = (byte) s; - } - - /** - * Computes the future value of a bytecode offset. - *

- * Note: it is possible to have several entries for the same instruction in - * the indexes and sizes: two entries (index=a,size=b) and - * (index=a,size=b') are equivalent to a single entry (index=a,size=b+b'). - * - * @param indexes - * current positions of the instructions to be resized. Each - * instruction must be designated by the index of its last - * byte, plus one (or, in other words, by the index of the - * first byte of the next instruction). - * @param sizes - * the number of bytes to be added to the above - * instructions. More precisely, for each i < len, - * sizes[i] bytes will be added at the end of the - * instruction designated by indexes[i] or, if - * sizes[i] is negative, the last | - * sizes[i]| bytes of the instruction will be removed - * (the instruction size must not become negative or - * null). - * @param begin - * index of the first byte of the source instruction. - * @param end - * index of the first byte of the target instruction. - * @return the future value of the given bytecode offset. - */ - static int getNewOffset(final int[] indexes, final int[] sizes, - final int begin, final int end) { - int offset = end - begin; - for (int i = 0; i < indexes.length; ++i) { - if (begin < indexes[i] && indexes[i] <= end) { - // forward jump - offset += sizes[i]; - } else if (end < indexes[i] && indexes[i] <= begin) { - // backward jump - offset -= sizes[i]; - } - } - return offset; - } - - /** - * Updates the offset of the given label. - * - * @param indexes - * current positions of the instructions to be resized. Each - * instruction must be designated by the index of its last - * byte, plus one (or, in other words, by the index of the - * first byte of the next instruction). - * @param sizes - * the number of bytes to be added to the above - * instructions. More precisely, for each i < len, - * sizes[i] bytes will be added at the end of the - * instruction designated by indexes[i] or, if - * sizes[i] is negative, the last | - * sizes[i]| bytes of the instruction will be removed - * (the instruction size must not become negative or - * null). - * @param label - * the label whose offset must be updated. - */ - static void getNewOffset(final int[] indexes, final int[] sizes, - final Label label) { - if ((label.status & Label.RESIZED) == 0) { - label.position = getNewOffset(indexes, sizes, 0, label.position); - label.status |= Label.RESIZED; - } - } } diff --git a/spring-core/src/main/java/org/springframework/asm/Opcodes.java b/spring-core/src/main/java/org/springframework/asm/Opcodes.java index a77a939056..a84b300b85 100644 --- a/spring-core/src/main/java/org/springframework/asm/Opcodes.java +++ b/spring-core/src/main/java/org/springframework/asm/Opcodes.java @@ -146,13 +146,17 @@ public interface Opcodes { */ int F_SAME1 = 4; - Integer TOP = 0; - Integer INTEGER = 1; - Integer FLOAT = 2; - Integer DOUBLE = 3; - Integer LONG = 4; - Integer NULL = 5; - Integer UNINITIALIZED_THIS = 6; + // Do not try to change the following code to use auto-boxing, + // these values are compared by reference and not by value + // The constructor of Integer was deprecated in 9 + // but we are stuck with it by backward compatibility + @SuppressWarnings("deprecation") Integer TOP = new Integer(0); + @SuppressWarnings("deprecation") Integer INTEGER = new Integer(1); + @SuppressWarnings("deprecation") Integer FLOAT = new Integer(2); + @SuppressWarnings("deprecation") Integer DOUBLE = new Integer(3); + @SuppressWarnings("deprecation") Integer LONG = new Integer(4); + @SuppressWarnings("deprecation") Integer NULL = new Integer(5); + @SuppressWarnings("deprecation") Integer UNINITIALIZED_THIS = new Integer(6); // opcodes // visit method (- = idem) diff --git a/spring-core/src/main/java/org/springframework/asm/Type.java b/spring-core/src/main/java/org/springframework/asm/Type.java index 0fa911538f..c40d51f427 100644 --- a/spring-core/src/main/java/org/springframework/asm/Type.java +++ b/spring-core/src/main/java/org/springframework/asm/Type.java @@ -377,7 +377,16 @@ public class Type { */ public static Type getReturnType(final String methodDescriptor) { char[] buf = methodDescriptor.toCharArray(); - return getType(buf, methodDescriptor.indexOf(')') + 1); + int off = 1; + while (true) { + char car = buf[off++]; + if (car == ')') { + return getType(buf, off); + } else if (car == 'L') { + while (buf[off++] != ';') { + } + } + } } /** @@ -625,9 +634,9 @@ public class Type { * @return the descriptor corresponding to this Java type. */ public String getDescriptor() { - StringBuilder sb = new StringBuilder(); - getDescriptor(sb); - return sb.toString(); + StringBuilder buf = new StringBuilder(); + getDescriptor(buf); + return buf.toString(); } /** @@ -643,34 +652,34 @@ public class Type { */ public static String getMethodDescriptor(final Type returnType, final Type... argumentTypes) { - StringBuilder sb = new StringBuilder(); - sb.append('('); + StringBuilder buf = new StringBuilder(); + buf.append('('); for (int i = 0; i < argumentTypes.length; ++i) { - argumentTypes[i].getDescriptor(sb); + argumentTypes[i].getDescriptor(buf); } - sb.append(')'); - returnType.getDescriptor(sb); - return sb.toString(); + buf.append(')'); + returnType.getDescriptor(buf); + return buf.toString(); } /** * Appends the descriptor corresponding to this Java type to the given - * string builder. + * string buffer. * - * @param sb - * the string builder to which the descriptor must be appended. + * @param buf + * the string buffer to which the descriptor must be appended. */ - private void getDescriptor(final StringBuilder sb) { + private void getDescriptor(final StringBuilder buf) { if (this.buf == null) { // descriptor is in byte 3 of 'off' for primitive types (buf == // null) - sb.append((char) ((off & 0xFF000000) >>> 24)); + buf.append((char) ((off & 0xFF000000) >>> 24)); } else if (sort == OBJECT) { - sb.append('L'); - sb.append(this.buf, off, len); - sb.append(';'); + buf.append('L'); + buf.append(this.buf, off, len); + buf.append(';'); } else { // sort == ARRAY || sort == METHOD - sb.append(this.buf, off, len); + buf.append(this.buf, off, len); } } @@ -700,9 +709,9 @@ public class Type { * @return the descriptor corresponding to the given class. */ public static String getDescriptor(final Class c) { - StringBuilder sb = new StringBuilder(); - getDescriptor(sb, c); - return sb.toString(); + StringBuilder buf = new StringBuilder(); + getDescriptor(buf, c); + return buf.toString(); } /** @@ -714,12 +723,12 @@ public class Type { */ public static String getConstructorDescriptor(final Constructor c) { Class[] parameters = c.getParameterTypes(); - StringBuilder sb = new StringBuilder(); - sb.append('('); + StringBuilder buf = new StringBuilder(); + buf.append('('); for (int i = 0; i < parameters.length; ++i) { - getDescriptor(sb, parameters[i]); + getDescriptor(buf, parameters[i]); } - return sb.append(")V").toString(); + return buf.append(")V").toString(); } /** @@ -731,25 +740,25 @@ public class Type { */ public static String getMethodDescriptor(final Method m) { Class[] parameters = m.getParameterTypes(); - StringBuilder sb = new StringBuilder(); - sb.append('('); + StringBuilder buf = new StringBuilder(); + buf.append('('); for (int i = 0; i < parameters.length; ++i) { - getDescriptor(sb, parameters[i]); + getDescriptor(buf, parameters[i]); } - sb.append(')'); - getDescriptor(sb, m.getReturnType()); - return sb.toString(); + buf.append(')'); + getDescriptor(buf, m.getReturnType()); + return buf.toString(); } /** - * Appends the descriptor of the given class to the given string builder. + * Appends the descriptor of the given class to the given string buffer. * - * @param sb + * @param buf * the string buffer to which the descriptor must be appended. * @param c * the class whose descriptor must be computed. */ - private static void getDescriptor(final StringBuilder sb, final Class c) { + private static void getDescriptor(final StringBuilder buf, final Class c) { Class d = c; while (true) { if (d.isPrimitive()) { @@ -773,20 +782,20 @@ public class Type { } else /* if (d == Long.TYPE) */{ car = 'J'; } - sb.append(car); + buf.append(car); return; } else if (d.isArray()) { - sb.append('['); + buf.append('['); d = d.getComponentType(); } else { - sb.append('L'); + buf.append('L'); String name = d.getName(); int len = name.length(); for (int i = 0; i < len; ++i) { char car = name.charAt(i); - sb.append(car == '.' ? '/' : car); + buf.append(car == '.' ? '/' : car); } - sb.append(';'); + buf.append(';'); return; } }