Java tutorial
// 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.objectweb.asm.commons; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.TypePath; /** * A {@link MethodVisitor} that renumbers local variables in their order of appearance. This adapter * allows one to easily add new local variables to a method. It may be used by inheriting from this * class, but the preferred way of using it is via delegation: the next visitor in the chain can * indeed add new locals when needed by calling {@link #newLocal} on this adapter (this requires a * reference back to this {@link LocalVariablesSorter}). * * @author Chris Nokleberg * @author Eugene Kuleshov * @author Eric Bruneton */ public class LocalVariablesSorter extends MethodVisitor { /** The type of the java.lang.Object class. */ private static final Type OBJECT_TYPE = Type.getObjectType("java/lang/Object"); /** * The mapping from old to new local variable indices. A local variable at index i of size 1 is * remapped to 'mapping[2*i]', while a local variable at index i of size 2 is remapped to * 'mapping[2*i+1]'. */ private int[] remappedVariableIndices = new int[40]; /** * The local variable types after remapping. The format of this array is the same as in {@link * MethodVisitor#visitFrame}, except that long and double types use two slots. */ private Object[] remappedLocalTypes = new Object[20]; /** The index of the first local variable, after formal parameters. */ protected final int firstLocal; /** The index of the next local variable to be created by {@link #newLocal}. */ protected int nextLocal; /** * Constructs a new {@link LocalVariablesSorter}. <i>Subclasses must not use this constructor</i>. * Instead, they must use the {@link #LocalVariablesSorter(int, int, String, MethodVisitor)} * version. * * @param access access flags of the adapted method. * @param descriptor the method's descriptor (see {@link Type}). * @param methodVisitor the method visitor to which this adapter delegates calls. * @throws IllegalStateException if a subclass calls this constructor. */ public LocalVariablesSorter(final int access, final String descriptor, final MethodVisitor methodVisitor) { this(/* latest api = */ Opcodes.ASM7, access, descriptor, methodVisitor); if (getClass() != LocalVariablesSorter.class) { throw new IllegalStateException(); } } /** * Constructs a new {@link LocalVariablesSorter}. * * @param api the ASM API version implemented by this visitor. Must be one of {@link * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. * @param access access flags of the adapted method. * @param descriptor the method's descriptor (see {@link Type}). * @param methodVisitor the method visitor to which this adapter delegates calls. */ protected LocalVariablesSorter(final int api, final int access, final String descriptor, final MethodVisitor methodVisitor) { super(api, methodVisitor); nextLocal = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0; for (Type argumentType : Type.getArgumentTypes(descriptor)) { nextLocal += argumentType.getSize(); } firstLocal = nextLocal; } @Override public void visitVarInsn(final int opcode, final int var) { Type varType; switch (opcode) { case Opcodes.LLOAD: case Opcodes.LSTORE: varType = Type.LONG_TYPE; break; case Opcodes.DLOAD: case Opcodes.DSTORE: varType = Type.DOUBLE_TYPE; break; case Opcodes.FLOAD: case Opcodes.FSTORE: varType = Type.FLOAT_TYPE; break; case Opcodes.ILOAD: case Opcodes.ISTORE: varType = Type.INT_TYPE; break; case Opcodes.ALOAD: case Opcodes.ASTORE: case Opcodes.RET: varType = OBJECT_TYPE; break; default: throw new IllegalArgumentException("Invalid opcode " + opcode); } super.visitVarInsn(opcode, remap(var, varType)); } @Override public void visitIincInsn(final int var, final int increment) { super.visitIincInsn(remap(var, Type.INT_TYPE), increment); } @Override public void visitMaxs(final int maxStack, final int maxLocals) { super.visitMaxs(maxStack, nextLocal); } @Override public void visitLocalVariable(final String name, final String descriptor, final String signature, final Label start, final Label end, final int index) { int remappedIndex = remap(index, Type.getType(descriptor)); super.visitLocalVariable(name, descriptor, signature, start, end, remappedIndex); } @Override public AnnotationVisitor visitLocalVariableAnnotation(final int typeRef, final TypePath typePath, final Label[] start, final Label[] end, final int[] index, final String descriptor, final boolean visible) { Type type = Type.getType(descriptor); int[] remappedIndex = new int[index.length]; for (int i = 0; i < remappedIndex.length; ++i) { remappedIndex[i] = remap(index[i], type); } return super.visitLocalVariableAnnotation(typeRef, typePath, start, end, remappedIndex, descriptor, visible); } @Override public void visitFrame(final int type, final int numLocal, final Object[] local, final int numStack, final Object[] stack) { if (type != Opcodes.F_NEW) { // Uncompressed frame. throw new IllegalArgumentException( "LocalVariablesSorter only accepts expanded frames (see ClassReader.EXPAND_FRAMES)"); } // Create a copy of remappedLocals. Object[] oldRemappedLocals = new Object[remappedLocalTypes.length]; System.arraycopy(remappedLocalTypes, 0, oldRemappedLocals, 0, oldRemappedLocals.length); updateNewLocals(remappedLocalTypes); // Copy the types from 'local' to 'remappedLocals'. 'remappedLocals' already contains the // variables added with 'newLocal'. int oldVar = 0; // Old local variable index. for (int i = 0; i < numLocal; ++i) { Object localType = local[i]; if (localType != Opcodes.TOP) { Type varType = OBJECT_TYPE; if (localType == Opcodes.INTEGER) { varType = Type.INT_TYPE; } else if (localType == Opcodes.FLOAT) { varType = Type.FLOAT_TYPE; } else if (localType == Opcodes.LONG) { varType = Type.LONG_TYPE; } else if (localType == Opcodes.DOUBLE) { varType = Type.DOUBLE_TYPE; } else if (localType instanceof String) { varType = Type.getObjectType((String) localType); } setFrameLocal(remap(oldVar, varType), localType); } oldVar += localType == Opcodes.LONG || localType == Opcodes.DOUBLE ? 2 : 1; } // Remove TOP after long and double types as well as trailing TOPs. oldVar = 0; int newVar = 0; int remappedNumLocal = 0; while (oldVar < remappedLocalTypes.length) { Object localType = remappedLocalTypes[oldVar]; oldVar += localType == Opcodes.LONG || localType == Opcodes.DOUBLE ? 2 : 1; if (localType != null && localType != Opcodes.TOP) { remappedLocalTypes[newVar++] = localType; remappedNumLocal = newVar; } else { remappedLocalTypes[newVar++] = Opcodes.TOP; } } // Visit the remapped frame. super.visitFrame(type, remappedNumLocal, remappedLocalTypes, numStack, stack); // Restore the original value of 'remappedLocals'. remappedLocalTypes = oldRemappedLocals; } // ----------------------------------------------------------------------------------------------- /** * Constructs a new local variable of the given type. * * @param type the type of the local variable to be created. * @return the identifier of the newly created local variable. */ public int newLocal(final Type type) { Object localType; switch (type.getSort()) { case Type.BOOLEAN: case Type.CHAR: case Type.BYTE: case Type.SHORT: case Type.INT: localType = Opcodes.INTEGER; break; case Type.FLOAT: localType = Opcodes.FLOAT; break; case Type.LONG: localType = Opcodes.LONG; break; case Type.DOUBLE: localType = Opcodes.DOUBLE; break; case Type.ARRAY: localType = type.getDescriptor(); break; case Type.OBJECT: localType = type.getInternalName(); break; default: throw new AssertionError(); } int local = newLocalMapping(type); setLocalType(local, type); setFrameLocal(local, localType); return local; } /** * Notifies subclasses that a new stack map frame is being visited. The array argument contains * the stack map frame types corresponding to the local variables added with {@link #newLocal}. * This method can update these types in place for the stack map frame being visited. The default * implementation of this method does nothing, i.e. a local variable added with {@link #newLocal} * will have the same type in all stack map frames. But this behavior is not always the desired * one, for instance if a local variable is added in the middle of a try/catch block: the frame * for the exception handler should have a TOP type for this new local. * * @param newLocals the stack map frame types corresponding to the local variables added with * {@link #newLocal} (and null for the others). The format of this array is the same as in * {@link MethodVisitor#visitFrame}, except that long and double types use two slots. The * types for the current stack map frame must be updated in place in this array. */ protected void updateNewLocals(final Object[] newLocals) { // The default implementation does nothing. } /** * Notifies subclasses that a local variable has been added or remapped. The default * implementation of this method does nothing. * * @param local a local variable identifier, as returned by {@link #newLocal}. * @param type the type of the value being stored in the local variable. */ protected void setLocalType(final int local, final Type type) { // The default implementation does nothing. } private void setFrameLocal(final int local, final Object type) { int numLocals = remappedLocalTypes.length; if (local >= numLocals) { Object[] newRemappedLocalTypes = new Object[Math.max(2 * numLocals, local + 1)]; System.arraycopy(remappedLocalTypes, 0, newRemappedLocalTypes, 0, numLocals); remappedLocalTypes = newRemappedLocalTypes; } remappedLocalTypes[local] = type; } private int remap(final int var, final Type type) { if (var + type.getSize() <= firstLocal) { return var; } int key = 2 * var + type.getSize() - 1; int size = remappedVariableIndices.length; if (key >= size) { int[] newRemappedVariableIndices = new int[Math.max(2 * size, key + 1)]; System.arraycopy(remappedVariableIndices, 0, newRemappedVariableIndices, 0, size); remappedVariableIndices = newRemappedVariableIndices; } int value = remappedVariableIndices[key]; if (value == 0) { value = newLocalMapping(type); setLocalType(value, type); remappedVariableIndices[key] = value + 1; } else { value--; } return value; } protected int newLocalMapping(final Type type) { int local = nextLocal; nextLocal += type.getSize(); return local; } }