| // 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.apache.tapestry5.internal.plastic.asm.commons; |
| |
| import org.apache.tapestry5.internal.plastic.asm.AnnotationVisitor; |
| import org.apache.tapestry5.internal.plastic.asm.Label; |
| import org.apache.tapestry5.internal.plastic.asm.MethodVisitor; |
| import org.apache.tapestry5.internal.plastic.asm.Opcodes; |
| import org.apache.tapestry5.internal.plastic.asm.Type; |
| import org.apache.tapestry5.internal.plastic.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.ASM8, 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}, {@link Opcodes#ASM7} or {@link |
| * Opcodes#ASM8}. |
| * @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; |
| } |
| } |