/*** | |
* ASM: a very small and fast Java bytecode manipulation framework | |
* Copyright (c) 2000-2005 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.Label; | |
import org.objectweb.asm.MethodAdapter; | |
import org.objectweb.asm.MethodVisitor; | |
import org.objectweb.asm.Opcodes; | |
import org.objectweb.asm.Type; | |
/** | |
* A {@link MethodAdapter} 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 MethodAdapter { | |
private final static Type OBJECT_TYPE = Type.getObjectType("java/lang/Object"); | |
/** | |
* Mapping from old to new local variable indexes. 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[] mapping = new int[40]; | |
/** | |
* Array used to store stack map local variable types after remapping. | |
*/ | |
private Object[] newLocals = new Object[20]; | |
/** | |
* Index of the first local variable, after formal parameters. | |
*/ | |
protected final int firstLocal; | |
/** | |
* Index of the next local variable to be created by {@link #newLocal}. | |
*/ | |
protected int nextLocal; | |
/** | |
* Indicates if at least one local variable has moved due to remapping. | |
*/ | |
private boolean changed; | |
/** | |
* Creates a new {@link LocalVariablesSorter}. | |
* | |
* @param access access flags of the adapted method. | |
* @param desc the method's descriptor (see {@link Type Type}). | |
* @param mv the method visitor to which this adapter delegates calls. | |
*/ | |
public LocalVariablesSorter( | |
final int access, | |
final String desc, | |
final MethodVisitor mv) | |
{ | |
super(mv); | |
Type[] args = Type.getArgumentTypes(desc); | |
nextLocal = (Opcodes.ACC_STATIC & access) != 0 ? 0 : 1; | |
for (int i = 0; i < args.length; i++) { | |
nextLocal += args[i].getSize(); | |
} | |
firstLocal = nextLocal; | |
} | |
public void visitVarInsn(final int opcode, final int var) { | |
Type type; | |
switch (opcode) { | |
case Opcodes.LLOAD: | |
case Opcodes.LSTORE: | |
type = Type.LONG_TYPE; | |
break; | |
case Opcodes.DLOAD: | |
case Opcodes.DSTORE: | |
type = Type.DOUBLE_TYPE; | |
break; | |
case Opcodes.FLOAD: | |
case Opcodes.FSTORE: | |
type = Type.FLOAT_TYPE; | |
break; | |
case Opcodes.ILOAD: | |
case Opcodes.ISTORE: | |
type = Type.INT_TYPE; | |
break; | |
case Opcodes.ALOAD: | |
case Opcodes.ASTORE: | |
type = OBJECT_TYPE; | |
break; | |
// case RET: | |
default: | |
type = Type.VOID_TYPE; | |
} | |
mv.visitVarInsn(opcode, remap(var, type)); | |
} | |
public void visitIincInsn(final int var, final int increment) { | |
mv.visitIincInsn(remap(var, Type.INT_TYPE), increment); | |
} | |
public void visitMaxs(final int maxStack, final int maxLocals) { | |
mv.visitMaxs(maxStack, nextLocal); | |
} | |
public void visitLocalVariable( | |
final String name, | |
final String desc, | |
final String signature, | |
final Label start, | |
final Label end, | |
final int index) | |
{ | |
int size = "J".equals(desc) || "D".equals(desc) ? 2 : 1; | |
int newIndex = remap(index, size); | |
mv.visitLocalVariable(name, desc, signature, start, end, newIndex); | |
} | |
public void visitFrame( | |
final int type, | |
final int nLocal, | |
final Object[] local, | |
final int nStack, | |
final Object[] stack) | |
{ | |
if (type != Opcodes.F_NEW) { // uncompressed frame | |
throw new IllegalStateException("ClassReader.accept() should be called with EXPAND_FRAMES flag"); | |
} | |
if (!changed) { // optimization for the case where mapping = identity | |
mv.visitFrame(type, nLocal, local, nStack, stack); | |
return; | |
} | |
// creates a copy of newLocals | |
Object[] oldLocals = new Object[newLocals.length]; | |
System.arraycopy(newLocals, 0, oldLocals, 0, oldLocals.length); | |
// copies types from 'local' to 'newLocals' | |
// 'newLocals' already contains the variables added with 'newLocal' | |
int index = 0; // old local variable index | |
int number = 0; // old local variable number | |
for (; number < nLocal; ++number) { | |
Object t = local[number]; | |
int size = t == Opcodes.LONG || t == Opcodes.DOUBLE ? 2 : 1; | |
if (t != Opcodes.TOP) { | |
setFrameLocal(remap(index, size), t); | |
} | |
index += size; | |
} | |
// removes TOP after long and double types as well as trailing TOPs | |
index = 0; | |
number = 0; | |
for (int i = 0; index < newLocals.length; ++i) { | |
Object t = newLocals[index++]; | |
if (t != null && t != Opcodes.TOP) { | |
newLocals[i] = t; | |
number = i + 1; | |
if (t == Opcodes.LONG || t == Opcodes.DOUBLE) { | |
index += 1; | |
} | |
} else { | |
newLocals[i] = Opcodes.TOP; | |
} | |
} | |
// visits remapped frame | |
mv.visitFrame(type, number, newLocals, nStack, stack); | |
// restores original value of 'newLocals' | |
newLocals = oldLocals; | |
} | |
// ------------- | |
/** | |
* Creates 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 t; | |
switch (type.getSort()) { | |
case Type.BOOLEAN: | |
case Type.CHAR: | |
case Type.BYTE: | |
case Type.SHORT: | |
case Type.INT: | |
t = Opcodes.INTEGER; | |
break; | |
case Type.FLOAT: | |
t = Opcodes.FLOAT; | |
break; | |
case Type.LONG: | |
t = Opcodes.LONG; | |
break; | |
case Type.DOUBLE: | |
t = Opcodes.DOUBLE; | |
break; | |
case Type.ARRAY: | |
t = type.getDescriptor(); | |
break; | |
// case Type.OBJECT: | |
default: | |
t = type.getInternalName(); | |
break; | |
} | |
int local = nextLocal; | |
setLocalType(local, type); | |
setFrameLocal(local, t); | |
nextLocal += type.getSize(); | |
return local; | |
} | |
/** | |
* Sets the current type of the given local variable. The default | |
* implementation of this method does nothing. | |
* | |
* @param local a local variable identifier, as returned by {@link #newLocal | |
* newLocal()}. | |
* @param type the type of the value being stored in the local variable | |
*/ | |
protected void setLocalType(final int local, final Type type) { | |
} | |
private void setFrameLocal(final int local, final Object type) { | |
int l = newLocals.length; | |
if (local >= l) { | |
Object[] a = new Object[Math.max(2 * l, local + 1)]; | |
System.arraycopy(newLocals, 0, a, 0, l); | |
newLocals = a; | |
} | |
newLocals[local] = type; | |
} | |
private int remap(final int var, final Type type) { | |
if (var < firstLocal) { | |
return var; | |
} | |
int key = 2 * var + type.getSize() - 1; | |
int size = mapping.length; | |
if (key >= size) { | |
int[] newMapping = new int[Math.max(2 * size, key + 1)]; | |
System.arraycopy(mapping, 0, newMapping, 0, size); | |
mapping = newMapping; | |
} | |
int value = mapping[key]; | |
if (value == 0) { | |
value = nextLocal + 1; | |
mapping[key] = value; | |
setLocalType(nextLocal, type); | |
nextLocal += type.getSize(); | |
} | |
if (value - 1 != var) { | |
changed = true; | |
} | |
return value - 1; | |
} | |
private int remap(final int var, final int size) { | |
if (var < firstLocal || !changed) { | |
return var; | |
} | |
int key = 2 * var + size - 1; | |
int value = key < mapping.length ? mapping[key] : 0; | |
if (value == 0) { | |
throw new IllegalStateException("Unknown local variable " + var); | |
} | |
return value - 1; | |
} | |
} |