org.objectweb.asm.commons.LocalVariablesSorter.java Source code

Java tutorial

Introduction

Here is the source code for org.objectweb.asm.commons.LocalVariablesSorter.java

Source

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