Upgrade to ASM 5.2

Issue: SPR-15071
This commit is contained in:
Juergen Hoeller 2016-12-30 11:20:54 +01:00
parent 311522bc86
commit ccabff6ba3
11 changed files with 436 additions and 708 deletions

View File

@ -89,7 +89,7 @@ public abstract class AnnotationVisitor {
* the actual value, whose type must be {@link Byte}, * the actual value, whose type must be {@link Byte},
* {@link Boolean}, {@link Character}, {@link Short}, * {@link Boolean}, {@link Character}, {@link Short},
* {@link Integer} , {@link Long}, {@link Float}, {@link Double}, * {@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, * value can also be an array of byte, boolean, short, char, int,
* long, float or double values (this is equivalent to using * long, float or double values (this is equivalent to using
* {@link #visitArray visitArray} and visiting each array element * {@link #visitArray visitArray} and visiting each array element

View File

@ -104,6 +104,21 @@ public class ClassReader {
*/ */
public static final int EXPAND_FRAMES = 8; 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 <i>not</i> 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. <i>The content of this array must not be * The class to be parsed. <i>The content of this array must not be
* modified. This field is intended for {@link Attribute} sub classes, and * modified. This field is intended for {@link Attribute} sub classes, and
@ -1062,6 +1077,10 @@ public class ClassReader {
readLabel(offset + readShort(u + 1), labels); readLabel(offset + readShort(u + 1), labels);
u += 3; u += 3;
break; break;
case ClassWriter.ASM_LABEL_INSN:
readLabel(offset + readUnsignedShort(u + 1), labels);
u += 3;
break;
case ClassWriter.LABELW_INSN: case ClassWriter.LABELW_INSN:
readLabel(offset + readInt(u + 1), labels); readLabel(offset + readInt(u + 1), labels);
u += 5; 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 // visits the instructions
int opcodeDelta = (context.flags & EXPAND_ASM_INSNS) == 0 ? -33 : 0;
u = codeStart; u = codeStart;
while (u < codeEnd) { while (u < codeEnd) {
int offset = u - codeStart; int offset = u - codeStart;
@ -1352,9 +1386,42 @@ public class ClassReader {
u += 3; u += 3;
break; break;
case ClassWriter.LABELW_INSN: case ClassWriter.LABELW_INSN:
mv.visitJumpInsn(opcode - 33, labels[offset + readInt(u + 1)]); mv.visitJumpInsn(opcode + opcodeDelta, labels[offset
+ readInt(u + 1)]);
u += 5; u += 5;
break; 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
// <l> with IFNOTxxx <l'> GOTO_W <l>, where IFNOTxxx is
// the "opposite" opcode of IFxxx (i.e., IFNE for IFEQ)
// and where <l'> 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: case ClassWriter.WIDE_INSN:
opcode = b[u + 1] & 0xFF; opcode = b[u + 1] & 0xFF;
if (opcode == Opcodes.IINC) { if (opcode == Opcodes.IINC) {
@ -1848,7 +1915,9 @@ public class ClassReader {
v += 2; v += 2;
break; break;
case 'Z': // pointer to CONSTANT_Boolean 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; v += 2;
break; break;
case 'S': // pointer to CONSTANT_Short case 'S': // pointer to CONSTANT_Short

View File

@ -58,8 +58,8 @@ public class ClassWriter extends ClassVisitor {
* {@link MethodVisitor#visitFrame} method are ignored, and the stack map * {@link MethodVisitor#visitFrame} method are ignored, and the stack map
* frames are recomputed from the methods bytecode. The arguments of the * frames are recomputed from the methods bytecode. The arguments of the
* {@link MethodVisitor#visitMaxs visitMaxs} method are also ignored and * {@link MethodVisitor#visitMaxs visitMaxs} method are also ignored and
* recomputed from the bytecode. In other words, computeFrames implies * recomputed from the bytecode. In other words, COMPUTE_FRAMES implies
* computeMaxs. * COMPUTE_MAXS.
* *
* @see #ClassWriter(int) * @see #ClassWriter(int)
*/ */
@ -167,6 +167,22 @@ public class ClassWriter extends ClassVisitor {
*/ */
static final int WIDE_INSN = 17; 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. * The instruction types of all JVM opcodes.
*/ */
@ -484,25 +500,19 @@ public class ClassWriter extends ClassVisitor {
MethodWriter lastMethod; MethodWriter lastMethod;
/** /**
* <tt>true</tt> if the maximum stack size and number of local variables * Indicates what must be automatically computed.
* must be automatically computed. *
* @see MethodWriter#compute
*/ */
private boolean computeMaxs; private int compute;
/** /**
* <tt>true</tt> if the stack map frames must be recomputed from scratch. * <tt>true</tt> 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; boolean hasAsmInsns;
/**
* <tt>true</tt> 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;
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Static initializer // Static initializer
@ -517,7 +527,7 @@ public class ClassWriter extends ClassVisitor {
String s = "AAAAAAAAAAAAAAAABCLMMDDDDDEEEEEEEEEEEEEEEEEEEEAAAAAAAADD" String s = "AAAAAAAAAAAAAAAABCLMMDDDDDEEEEEEEEEEEEEEEEEEEEAAAAAAAADD"
+ "DDDEEEEEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "DDDEEEEEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAANAAAAAAAAAAAAAAAAAAAAJJJJJJJJJJJJJJJJDOPAA" + "AAAAAAAAAAAAAAAAANAAAAAAAAAAAAAAAAAAAAJJJJJJJJJJJJJJJJDOPAA"
+ "AAAAGGGGGGGHIFBFAAFFAARQJJKKJJJJJJJJJJJJJJJJJJ"; + "AAAAGGGGGGGHIFBFAAFFAARQJJKKSSSSSSSSSSSSSSSSSS";
for (i = 0; i < b.length; ++i) { for (i = 0; i < b.length; ++i) {
b[i] = (byte) (s.charAt(i) - 'A'); 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 // // temporary opcodes used internally by ASM - see Label and
// MethodWriter // MethodWriter
// for (i = 202; i < 220; ++i) { // for (i = 202; i < 220; ++i) {
// b[i] = LABEL_INSN; // b[i] = ASM_LABEL_INSN;
// } // }
// //
// // LDC(_W) instructions // // LDC(_W) instructions
@ -614,8 +624,9 @@ public class ClassWriter extends ClassVisitor {
key2 = new Item(); key2 = new Item();
key3 = new Item(); key3 = new Item();
key4 = new Item(); key4 = new Item();
this.computeMaxs = (flags & COMPUTE_MAXS) != 0; this.compute = (flags & COMPUTE_FRAMES) != 0 ? MethodWriter.FRAMES
this.computeFrames = (flags & COMPUTE_FRAMES) != 0; : ((flags & COMPUTE_MAXS) != 0 ? MethodWriter.MAXS
: MethodWriter.NOTHING);
} }
/** /**
@ -645,9 +656,9 @@ public class ClassWriter extends ClassVisitor {
* @param flags * @param flags
* option flags that can be used to modify the default behavior * option flags that can be used to modify the default behavior
* of this class. <i>These option flags do not affect methods * of this class. <i>These option flags do not affect methods
* that are copied as is in the new class. This means that the * that are copied as is in the new class. This means that
* maximum stack size nor the stack frames will be computed for * neither the maximum stack size nor the stack frames will be
* these methods</i>. See {@link #COMPUTE_MAXS}, * computed for these methods</i>. See {@link #COMPUTE_MAXS},
* {@link #COMPUTE_FRAMES}. * {@link #COMPUTE_FRAMES}.
*/ */
public ClassWriter(final ClassReader classReader, final int flags) { 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, public final MethodVisitor visitMethod(final int access, final String name,
final String desc, final String signature, final String[] exceptions) { final String desc, final String signature, final String[] exceptions) {
return new MethodWriter(this, access, name, desc, signature, return new MethodWriter(this, access, name, desc, signature,
exceptions, computeMaxs, computeFrames); exceptions, compute);
} }
@Override @Override
@ -977,22 +988,20 @@ public class ClassWriter extends ClassVisitor {
if (attrs != null) { if (attrs != null) {
attrs.put(this, null, 0, -1, -1, out); attrs.put(this, null, 0, -1, -1, out);
} }
if (invalidFrames) { if (hasAsmInsns) {
anns = null; anns = null;
ianns = null; ianns = null;
attrs = null; attrs = null;
innerClassesCount = 0; innerClassesCount = 0;
innerClasses = null; innerClasses = null;
bootstrapMethodsCount = 0;
bootstrapMethods = null;
firstField = null; firstField = null;
lastField = null; lastField = null;
firstMethod = null; firstMethod = null;
lastMethod = null; lastMethod = null;
computeMaxs = false; compute = MethodWriter.INSERTED_FRAMES;
computeFrames = true; hasAsmInsns = false;
invalidFrames = false; new ClassReader(out.data).accept(this, ClassReader.EXPAND_FRAMES
new ClassReader(out.data).accept(this, ClassReader.SKIP_FRAMES); | ClassReader.EXPAND_ASM_INSNS);
return toByteArray(); return toByteArray();
} }
return out.data; return out.data;

View File

@ -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;
}
}

View File

@ -34,7 +34,7 @@ package org.springframework.asm;
* *
* @author Eric Bruneton * @author Eric Bruneton
*/ */
final class Frame { class Frame {
/* /*
* Frames are computed in a two steps process: during the visit of each * 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 * When the stack map frames are completely computed, this field is the
* actual number of types in {@link #outputStack}. * actual number of types in {@link #outputStack}.
*/ */
private int outputStackTop; int outputStackTop;
/** /**
* Number of types that are initialized in the basic block. * Number of types that are initialized in the basic block.
@ -520,6 +520,110 @@ final class Frame {
*/ */
private int[] initializations; 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. * 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 // pushes the type on the output stack
outputStack[outputStackTop++] = type; 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; int top = owner.inputStackTop + outputStackTop;
if (top > owner.outputStackMax) { if (top > owner.outputStackMax) {
owner.outputStackMax = top; owner.outputStackMax = top;
@ -809,7 +913,7 @@ final class Frame {
* @param maxLocals * @param maxLocals
* the maximum number of local variables of this method. * 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) { final Type[] args, final int maxLocals) {
inputLocals = new int[maxLocals]; inputLocals = new int[maxLocals];
inputStack = new int[0]; inputStack = new int[0];
@ -1283,7 +1387,7 @@ final class Frame {
* @return <tt>true</tt> if the input frame of the given label has been * @return <tt>true</tt> if the input frame of the given label has been
* changed by this operation. * 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; boolean changed = false;
int i, s, dim, kind, t; int i, s, dim, kind, t;

View File

@ -201,6 +201,7 @@ final class Item {
* @param strVal3 * @param strVal3
* third part of the value of this item. * third part of the value of this item.
*/ */
@SuppressWarnings("fallthrough")
void set(final int type, final String strVal1, final String strVal2, void set(final int type, final String strVal1, final String strVal2,
final String strVal3) { final String strVal3) {
this.type = type; this.type = type;
@ -210,8 +211,6 @@ final class Item {
switch (type) { switch (type) {
case ClassWriter.CLASS: case ClassWriter.CLASS:
this.intVal = 0; // intVal of a class must be zero, see visitInnerClass this.intVal = 0; // intVal of a class must be zero, see visitInnerClass
hashCode = 0x7FFFFFFF & (type + strVal1.hashCode());
return;
case ClassWriter.UTF8: case ClassWriter.UTF8:
case ClassWriter.STR: case ClassWriter.STR:
case ClassWriter.MTYPE: case ClassWriter.MTYPE:

View File

@ -364,9 +364,8 @@ public class Label {
* small to store the offset. In such a case the corresponding jump * small to store the offset. In such a case the corresponding jump
* instruction is replaced with a pseudo instruction (using unused * instruction is replaced with a pseudo instruction (using unused
* opcodes) using an unsigned two bytes offset. These pseudo * opcodes) using an unsigned two bytes offset. These pseudo
* instructions will need to be replaced with true instructions with * instructions will be replaced with standard bytecode instructions
* wider offsets (4 bytes instead of 2). This is done in * with wider offsets (4 bytes instead of 2), in ClassReader.
* {@link MethodWriter#resizeInstructions}.
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if this label has already been resolved, or if it has not * if this label has already been resolved, or if it has not
* been created by the given code writer. * been created by the given code writer.

View File

@ -33,15 +33,16 @@ package org.springframework.asm;
* A visitor to visit a Java method. The methods of this class must be called in * A visitor to visit a Java method. The methods of this class must be called in
* the following order: ( <tt>visitParameter</tt> )* [ * the following order: ( <tt>visitParameter</tt> )* [
* <tt>visitAnnotationDefault</tt> ] ( <tt>visitAnnotation</tt> | * <tt>visitAnnotationDefault</tt> ] ( <tt>visitAnnotation</tt> |
* <tt>visitTypeAnnotation</tt> | <tt>visitAttribute</tt> )* [ * <tt>visitParameterAnnotation</tt> <tt>visitTypeAnnotation</tt> |
* <tt>visitCode</tt> ( <tt>visitFrame</tt> | <tt>visit<i>X</i>Insn</tt> | * <tt>visitAttribute</tt> )* [ <tt>visitCode</tt> ( <tt>visitFrame</tt> |
* <tt>visitLabel</tt> | <tt>visitInsnAnnotation</tt> | * <tt>visit<i>X</i>Insn</tt> | <tt>visitLabel</tt> |
* <tt>visitTryCatchBlock</tt> | <tt>visitTryCatchBlockAnnotation</tt> | * <tt>visitInsnAnnotation</tt> | <tt>visitTryCatchBlock</tt> |
* <tt>visitLocalVariable</tt> | <tt>visitLocalVariableAnnotation</tt> | * <tt>visitTryCatchAnnotation</tt> | <tt>visitLocalVariable</tt> |
* <tt>visitLineNumber</tt> )* <tt>visitMaxs</tt> ] <tt>visitEnd</tt>. In * <tt>visitLocalVariableAnnotation</tt> | <tt>visitLineNumber</tt> )*
* addition, the <tt>visit<i>X</i>Insn</tt> and <tt>visitLabel</tt> methods must * <tt>visitMaxs</tt> ] <tt>visitEnd</tt>. In addition, the
* be called in the sequential order of the bytecode instructions of the visited * <tt>visit<i>X</i>Insn</tt> and <tt>visitLabel</tt> methods must be called in
* code, <tt>visitInsnAnnotation</tt> must be called <i>after</i> the annotated * the sequential order of the bytecode instructions of the visited code,
* <tt>visitInsnAnnotation</tt> must be called <i>after</i> the annotated
* instruction, <tt>visitTryCatchBlock</tt> must be called <i>before</i> the * instruction, <tt>visitTryCatchBlock</tt> must be called <i>before</i> the
* labels passed as arguments have been visited, * labels passed as arguments have been visited,
* <tt>visitTryCatchBlockAnnotation</tt> must be called <i>after</i> the * <tt>visitTryCatchBlockAnnotation</tt> must be called <i>after</i> the

View File

@ -99,7 +99,19 @@ class MethodWriter extends MethodVisitor {
* *
* @see #compute * @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 * Indicates that the maximum stack size and number of local variables must
@ -107,14 +119,14 @@ class MethodWriter extends MethodVisitor {
* *
* @see #compute * @see #compute
*/ */
private static final int MAXS = 1; static final int MAXS = 2;
/** /**
* Indicates that nothing must be automatically computed. * Indicates that nothing must be automatically computed.
* *
* @see #compute * @see #compute
*/ */
private static final int NOTHING = 2; static final int NOTHING = 3;
/** /**
* The class writer to which this method must be added. * The class writer to which this method must be added.
@ -354,11 +366,6 @@ class MethodWriter extends MethodVisitor {
*/ */
private Attribute cattrs; 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. * The number of subroutines in this method.
*/ */
@ -380,6 +387,7 @@ class MethodWriter extends MethodVisitor {
* Indicates what must be automatically computed. * Indicates what must be automatically computed.
* *
* @see #FRAMES * @see #FRAMES
* @see #INSERTED_FRAMES
* @see #MAXS * @see #MAXS
* @see #NOTHING * @see #NOTHING
*/ */
@ -442,17 +450,12 @@ class MethodWriter extends MethodVisitor {
* @param exceptions * @param exceptions
* the internal names of the method's exceptions. May be * the internal names of the method's exceptions. May be
* <tt>null</tt>. * <tt>null</tt>.
* @param computeMaxs * @param compute
* <tt>true</tt> if the maximum stack size and number of local * Indicates what must be automatically computed (see #compute).
* variables must be automatically computed.
* @param computeFrames
* <tt>true</tt> if the stack map tables must be recomputed from
* scratch.
*/ */
MethodWriter(final ClassWriter cw, final int access, final String name, MethodWriter(final ClassWriter cw, final int access, final String name,
final String desc, final String signature, final String desc, final String signature,
final String[] exceptions, final boolean computeMaxs, final String[] exceptions, final int compute) {
final boolean computeFrames) {
super(Opcodes.ASM5); super(Opcodes.ASM5);
if (cw.firstMethod == null) { if (cw.firstMethod == null) {
cw.firstMethod = this; cw.firstMethod = this;
@ -478,8 +481,8 @@ class MethodWriter extends MethodVisitor {
this.exceptions[i] = cw.newClass(exceptions[i]); this.exceptions[i] = cw.newClass(exceptions[i]);
} }
} }
this.compute = computeFrames ? FRAMES : (computeMaxs ? MAXS : NOTHING); this.compute = compute;
if (computeMaxs || computeFrames) { if (compute != NOTHING) {
// updates maxLocals // updates maxLocals
int size = Type.getArgumentsAndReturnSizes(descriptor) >> 2; int size = Type.getArgumentsAndReturnSizes(descriptor) >> 2;
if ((access & Opcodes.ACC_STATIC) != 0) { if ((access & Opcodes.ACC_STATIC) != 0) {
@ -614,7 +617,29 @@ class MethodWriter extends MethodVisitor {
return; 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) { if (previousFrame == null) {
visitImplicitFirstFrame(); visitImplicitFirstFrame();
} }
@ -718,7 +743,7 @@ class MethodWriter extends MethodVisitor {
// update currentBlock // update currentBlock
// Label currentBlock = this.currentBlock; // Label currentBlock = this.currentBlock;
if (currentBlock != null) { if (currentBlock != null) {
if (compute == FRAMES) { if (compute == FRAMES || compute == INSERTED_FRAMES) {
currentBlock.frame.execute(opcode, 0, null, null); currentBlock.frame.execute(opcode, 0, null, null);
} else { } else {
// updates current and max stack sizes // updates current and max stack sizes
@ -741,7 +766,7 @@ class MethodWriter extends MethodVisitor {
lastCodeOffset = code.length; lastCodeOffset = code.length;
// Label currentBlock = this.currentBlock; // Label currentBlock = this.currentBlock;
if (currentBlock != null) { if (currentBlock != null) {
if (compute == FRAMES) { if (compute == FRAMES || compute == INSERTED_FRAMES) {
currentBlock.frame.execute(opcode, operand, null, null); currentBlock.frame.execute(opcode, operand, null, null);
} else if (opcode != Opcodes.NEWARRAY) { } else if (opcode != Opcodes.NEWARRAY) {
// updates current and max stack sizes only for NEWARRAY // updates current and max stack sizes only for NEWARRAY
@ -766,7 +791,7 @@ class MethodWriter extends MethodVisitor {
lastCodeOffset = code.length; lastCodeOffset = code.length;
// Label currentBlock = this.currentBlock; // Label currentBlock = this.currentBlock;
if (currentBlock != null) { if (currentBlock != null) {
if (compute == FRAMES) { if (compute == FRAMES || compute == INSERTED_FRAMES) {
currentBlock.frame.execute(opcode, var, null, null); currentBlock.frame.execute(opcode, var, null, null);
} else { } else {
// updates current and max stack sizes // updates current and max stack sizes
@ -826,7 +851,7 @@ class MethodWriter extends MethodVisitor {
Item i = cw.newClassItem(type); Item i = cw.newClassItem(type);
// Label currentBlock = this.currentBlock; // Label currentBlock = this.currentBlock;
if (currentBlock != null) { if (currentBlock != null) {
if (compute == FRAMES) { if (compute == FRAMES || compute == INSERTED_FRAMES) {
currentBlock.frame.execute(opcode, code.length, cw, i); currentBlock.frame.execute(opcode, code.length, cw, i);
} else if (opcode == Opcodes.NEW) { } else if (opcode == Opcodes.NEW) {
// updates current and max stack sizes only if opcode == 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); Item i = cw.newFieldItem(owner, name, desc);
// Label currentBlock = this.currentBlock; // Label currentBlock = this.currentBlock;
if (currentBlock != null) { if (currentBlock != null) {
if (compute == FRAMES) { if (compute == FRAMES || compute == INSERTED_FRAMES) {
currentBlock.frame.execute(opcode, 0, cw, i); currentBlock.frame.execute(opcode, 0, cw, i);
} else { } else {
int size; int size;
@ -889,7 +914,7 @@ class MethodWriter extends MethodVisitor {
int argSize = i.intVal; int argSize = i.intVal;
// Label currentBlock = this.currentBlock; // Label currentBlock = this.currentBlock;
if (currentBlock != null) { if (currentBlock != null) {
if (compute == FRAMES) { if (compute == FRAMES || compute == INSERTED_FRAMES) {
currentBlock.frame.execute(opcode, 0, cw, i); currentBlock.frame.execute(opcode, 0, cw, i);
} else { } else {
/* /*
@ -941,7 +966,7 @@ class MethodWriter extends MethodVisitor {
int argSize = i.intVal; int argSize = i.intVal;
// Label currentBlock = this.currentBlock; // Label currentBlock = this.currentBlock;
if (currentBlock != null) { if (currentBlock != null) {
if (compute == FRAMES) { if (compute == FRAMES || compute == INSERTED_FRAMES) {
currentBlock.frame.execute(Opcodes.INVOKEDYNAMIC, 0, cw, i); currentBlock.frame.execute(Opcodes.INVOKEDYNAMIC, 0, cw, i);
} else { } else {
/* /*
@ -975,7 +1000,9 @@ class MethodWriter extends MethodVisitor {
} }
@Override @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; lastCodeOffset = code.length;
Label nextInsn = null; Label nextInsn = null;
// Label currentBlock = this.currentBlock; // Label currentBlock = this.currentBlock;
@ -990,6 +1017,8 @@ class MethodWriter extends MethodVisitor {
// creates a Label for the next basic block // creates a Label for the next basic block
nextInsn = new Label(); nextInsn = new Label();
} }
} else if (compute == INSERTED_FRAMES) {
currentBlock.frame.execute(opcode, 0, null, null);
} else { } else {
if (opcode == Opcodes.JSR) { if (opcode == Opcodes.JSR) {
if ((label.status & Label.SUBROUTINE) == 0) { if ((label.status & Label.SUBROUTINE) == 0) {
@ -1041,6 +1070,14 @@ class MethodWriter extends MethodVisitor {
code.putByte(200); // GOTO_W code.putByte(200); // GOTO_W
} }
label.put(this, code, code.length - 1, true); 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 { } else {
/* /*
* case of a backward jump with an offset >= -32768, or of a forward * case of a backward jump with an offset >= -32768, or of a forward
@ -1068,7 +1105,7 @@ class MethodWriter extends MethodVisitor {
@Override @Override
public void visitLabel(final Label label) { public void visitLabel(final Label label) {
// resolves previous forward references to label, if any // 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 // updates currentBlock
if ((label.status & Label.DEBUG) != 0) { if ((label.status & Label.DEBUG) != 0) {
return; return;
@ -1101,6 +1138,18 @@ class MethodWriter extends MethodVisitor {
previousBlock.successor = label; previousBlock.successor = label;
} }
previousBlock = 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) { } else if (compute == MAXS) {
if (currentBlock != null) { if (currentBlock != null) {
// ends current block (with one new successor) // ends current block (with one new successor)
@ -1126,7 +1175,7 @@ class MethodWriter extends MethodVisitor {
Item i = cw.newConstItem(cst); Item i = cw.newConstItem(cst);
// Label currentBlock = this.currentBlock; // Label currentBlock = this.currentBlock;
if (currentBlock != null) { if (currentBlock != null) {
if (compute == FRAMES) { if (compute == FRAMES || compute == INSERTED_FRAMES) {
currentBlock.frame.execute(Opcodes.LDC, 0, cw, i); currentBlock.frame.execute(Opcodes.LDC, 0, cw, i);
} else { } else {
int size; int size;
@ -1158,7 +1207,7 @@ class MethodWriter extends MethodVisitor {
public void visitIincInsn(final int var, final int increment) { public void visitIincInsn(final int var, final int increment) {
lastCodeOffset = code.length; lastCodeOffset = code.length;
if (currentBlock != null) { if (currentBlock != null) {
if (compute == FRAMES) { if (compute == FRAMES || compute == INSERTED_FRAMES) {
currentBlock.frame.execute(Opcodes.IINC, var, null, null); currentBlock.frame.execute(Opcodes.IINC, var, null, null);
} }
} }
@ -1245,7 +1294,7 @@ class MethodWriter extends MethodVisitor {
Item i = cw.newClassItem(desc); Item i = cw.newClassItem(desc);
// Label currentBlock = this.currentBlock; // Label currentBlock = this.currentBlock;
if (currentBlock != null) { if (currentBlock != null) {
if (compute == FRAMES) { if (compute == FRAMES || compute == INSERTED_FRAMES) {
currentBlock.frame.execute(Opcodes.MULTIANEWARRAY, dims, cw, i); currentBlock.frame.execute(Opcodes.MULTIANEWARRAY, dims, cw, i);
} else { } else {
// updates current stack size (max stack size unchanged because // updates current stack size (max stack size unchanged because
@ -1401,14 +1450,6 @@ class MethodWriter extends MethodVisitor {
@Override @Override
public void visitMaxs(final int maxStack, final int maxLocals) { 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) { if (ClassReader.FRAMES && compute == FRAMES) {
// completes the control flow graph with exception handler blocks // completes the control flow graph with exception handler blocks
Handler handler = firstHandler; Handler handler = firstHandler;
@ -1439,8 +1480,8 @@ class MethodWriter extends MethodVisitor {
// creates and visits the first (implicit) frame // creates and visits the first (implicit) frame
Frame f = labels.frame; Frame f = labels.frame;
Type[] args = Type.getArgumentTypes(descriptor); f.initInputFrame(cw, access, Type.getArgumentTypes(descriptor),
f.initInputFrame(cw, access, args, this.maxLocals); this.maxLocals);
visitFrame(f); visitFrame(f);
/* /*
@ -1688,7 +1729,9 @@ class MethodWriter extends MethodVisitor {
} else { } else {
currentBlock.outputStackMax = maxStackSize; 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); 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.
* <p>
* <i>This method must be called after all the method that is being built
* has been visited</i>. 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 <l> instruction with
// IFNOTxxx <l'> GOTO_W <l>, where IFNOTxxx
// is the "opposite" opcode of IFxxx (i.e.,
// IFNE for IFEQ) and where <l'> 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
// <l> with IFNOTxxx <l'> GOTO_W <l>, where IFNOTxxx is
// the "opposite" opcode of IFxxx (i.e., IFNE for IFEQ)
// and where <l'> 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.
* <p>
* Note: it is possible to have several entries for the same instruction in
* the <tt>indexes</tt> and <tt>sizes</tt>: 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 <i>last</i>
* byte, plus one (or, in other words, by the index of the
* <i>first</i> byte of the <i>next</i> instruction).
* @param sizes
* the number of bytes to be <i>added</i> to the above
* instructions. More precisely, for each i < <tt>len</tt>,
* <tt>sizes</tt>[i] bytes will be added at the end of the
* instruction designated by <tt>indexes</tt>[i] or, if
* <tt>sizes</tt>[i] is negative, the <i>last</i> |
* <tt>sizes[i]</tt>| bytes of the instruction will be removed
* (the instruction size <i>must not</i> 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 <i>last</i>
* byte, plus one (or, in other words, by the index of the
* <i>first</i> byte of the <i>next</i> instruction).
* @param sizes
* the number of bytes to be <i>added</i> to the above
* instructions. More precisely, for each i < <tt>len</tt>,
* <tt>sizes</tt>[i] bytes will be added at the end of the
* instruction designated by <tt>indexes</tt>[i] or, if
* <tt>sizes</tt>[i] is negative, the <i>last</i> |
* <tt>sizes[i]</tt>| bytes of the instruction will be removed
* (the instruction size <i>must not</i> 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;
}
}
} }

View File

@ -146,13 +146,17 @@ public interface Opcodes {
*/ */
int F_SAME1 = 4; int F_SAME1 = 4;
Integer TOP = 0; // Do not try to change the following code to use auto-boxing,
Integer INTEGER = 1; // these values are compared by reference and not by value
Integer FLOAT = 2; // The constructor of Integer was deprecated in 9
Integer DOUBLE = 3; // but we are stuck with it by backward compatibility
Integer LONG = 4; @SuppressWarnings("deprecation") Integer TOP = new Integer(0);
Integer NULL = 5; @SuppressWarnings("deprecation") Integer INTEGER = new Integer(1);
Integer UNINITIALIZED_THIS = 6; @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) // opcodes // visit method (- = idem)

View File

@ -377,7 +377,16 @@ public class Type {
*/ */
public static Type getReturnType(final String methodDescriptor) { public static Type getReturnType(final String methodDescriptor) {
char[] buf = methodDescriptor.toCharArray(); 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. * @return the descriptor corresponding to this Java type.
*/ */
public String getDescriptor() { public String getDescriptor() {
StringBuilder sb = new StringBuilder(); StringBuilder buf = new StringBuilder();
getDescriptor(sb); getDescriptor(buf);
return sb.toString(); return buf.toString();
} }
/** /**
@ -643,34 +652,34 @@ public class Type {
*/ */
public static String getMethodDescriptor(final Type returnType, public static String getMethodDescriptor(final Type returnType,
final Type... argumentTypes) { final Type... argumentTypes) {
StringBuilder sb = new StringBuilder(); StringBuilder buf = new StringBuilder();
sb.append('('); buf.append('(');
for (int i = 0; i < argumentTypes.length; ++i) { for (int i = 0; i < argumentTypes.length; ++i) {
argumentTypes[i].getDescriptor(sb); argumentTypes[i].getDescriptor(buf);
} }
sb.append(')'); buf.append(')');
returnType.getDescriptor(sb); returnType.getDescriptor(buf);
return sb.toString(); return buf.toString();
} }
/** /**
* Appends the descriptor corresponding to this Java type to the given * Appends the descriptor corresponding to this Java type to the given
* string builder. * string buffer.
* *
* @param sb * @param buf
* the string builder to which the descriptor must be appended. * 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) { if (this.buf == null) {
// descriptor is in byte 3 of 'off' for primitive types (buf == // descriptor is in byte 3 of 'off' for primitive types (buf ==
// null) // null)
sb.append((char) ((off & 0xFF000000) >>> 24)); buf.append((char) ((off & 0xFF000000) >>> 24));
} else if (sort == OBJECT) { } else if (sort == OBJECT) {
sb.append('L'); buf.append('L');
sb.append(this.buf, off, len); buf.append(this.buf, off, len);
sb.append(';'); buf.append(';');
} else { // sort == ARRAY || sort == METHOD } 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. * @return the descriptor corresponding to the given class.
*/ */
public static String getDescriptor(final Class<?> c) { public static String getDescriptor(final Class<?> c) {
StringBuilder sb = new StringBuilder(); StringBuilder buf = new StringBuilder();
getDescriptor(sb, c); getDescriptor(buf, c);
return sb.toString(); return buf.toString();
} }
/** /**
@ -714,12 +723,12 @@ public class Type {
*/ */
public static String getConstructorDescriptor(final Constructor<?> c) { public static String getConstructorDescriptor(final Constructor<?> c) {
Class<?>[] parameters = c.getParameterTypes(); Class<?>[] parameters = c.getParameterTypes();
StringBuilder sb = new StringBuilder(); StringBuilder buf = new StringBuilder();
sb.append('('); buf.append('(');
for (int i = 0; i < parameters.length; ++i) { 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) { public static String getMethodDescriptor(final Method m) {
Class<?>[] parameters = m.getParameterTypes(); Class<?>[] parameters = m.getParameterTypes();
StringBuilder sb = new StringBuilder(); StringBuilder buf = new StringBuilder();
sb.append('('); buf.append('(');
for (int i = 0; i < parameters.length; ++i) { for (int i = 0; i < parameters.length; ++i) {
getDescriptor(sb, parameters[i]); getDescriptor(buf, parameters[i]);
} }
sb.append(')'); buf.append(')');
getDescriptor(sb, m.getReturnType()); getDescriptor(buf, m.getReturnType());
return sb.toString(); 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. * the string buffer to which the descriptor must be appended.
* @param c * @param c
* the class whose descriptor must be computed. * 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; Class<?> d = c;
while (true) { while (true) {
if (d.isPrimitive()) { if (d.isPrimitive()) {
@ -773,20 +782,20 @@ public class Type {
} else /* if (d == Long.TYPE) */{ } else /* if (d == Long.TYPE) */{
car = 'J'; car = 'J';
} }
sb.append(car); buf.append(car);
return; return;
} else if (d.isArray()) { } else if (d.isArray()) {
sb.append('['); buf.append('[');
d = d.getComponentType(); d = d.getComponentType();
} else { } else {
sb.append('L'); buf.append('L');
String name = d.getName(); String name = d.getName();
int len = name.length(); int len = name.length();
for (int i = 0; i < len; ++i) { for (int i = 0; i < len; ++i) {
char car = name.charAt(i); char car = name.charAt(i);
sb.append(car == '.' ? '/' : car); buf.append(car == '.' ? '/' : car);
} }
sb.append(';'); buf.append(';');
return; return;
} }
} }