Back to index...
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
/*
 * This file is available under and governed by the GNU General Public
 * License version 2 only, as published by the Free Software Foundation.
 * However, the following notice accompanied the original version of this
 * file:
 *
 * 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 jdk.internal.org.objectweb.asm.commons;
import jdk.internal.org.objectweb.asm.AnnotationVisitor;
import jdk.internal.org.objectweb.asm.Label;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.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.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;
    }
}
Back to index...