001// ASM: a very small and fast Java bytecode manipulation framework
002// Copyright (c) 2000-2011 INRIA, France Telecom
003// All rights reserved.
004//
005// Redistribution and use in source and binary forms, with or without
006// modification, are permitted provided that the following conditions
007// are met:
008// 1. Redistributions of source code must retain the above copyright
009//    notice, this list of conditions and the following disclaimer.
010// 2. Redistributions in binary form must reproduce the above copyright
011//    notice, this list of conditions and the following disclaimer in the
012//    documentation and/or other materials provided with the distribution.
013// 3. Neither the name of the copyright holders nor the names of its
014//    contributors may be used to endorse or promote products derived from
015//    this software without specific prior written permission.
016//
017// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
018// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
019// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
020// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
021// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
022// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
023// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
024// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
025// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
026// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
027// THE POSSIBILITY OF SUCH DAMAGE.
028package io.ebean.enhance.asm.commons;
029
030import io.ebean.enhance.asm.AnnotationVisitor;
031import io.ebean.enhance.asm.Label;
032import io.ebean.enhance.asm.MethodVisitor;
033import io.ebean.enhance.asm.Opcodes;
034import io.ebean.enhance.asm.Type;
035import io.ebean.enhance.asm.TypePath;
036
037/**
038 * A {@link MethodVisitor} that renumbers local variables in their order of appearance. This adapter
039 * allows one to easily add new local variables to a method. It may be used by inheriting from this
040 * class, but the preferred way of using it is via delegation: the next visitor in the chain can
041 * indeed add new locals when needed by calling {@link #newLocal} on this adapter (this requires a
042 * reference back to this {@link LocalVariablesSorter}).
043 *
044 * @author Chris Nokleberg
045 * @author Eugene Kuleshov
046 * @author Eric Bruneton
047 */
048public class LocalVariablesSorter extends MethodVisitor {
049
050  /** The type of the java.lang.Object class. */
051  private static final Type OBJECT_TYPE = Type.getObjectType("java/lang/Object");
052
053  /**
054   * The mapping from old to new local variable indices. A local variable at index i of size 1 is
055   * remapped to 'mapping[2*i]', while a local variable at index i of size 2 is remapped to
056   * 'mapping[2*i+1]'.
057   */
058  private int[] remappedVariableIndices = new int[40];
059
060  /**
061   * The local variable types after remapping. The format of this array is the same as in {@link
062   * MethodVisitor#visitFrame}, except that long and double types use two slots.
063   */
064  private Object[] remappedLocalTypes = new Object[20];
065
066  /** The index of the first local variable, after formal parameters. */
067  protected final int firstLocal;
068
069  /** The index of the next local variable to be created by {@link #newLocal}. */
070  protected int nextLocal;
071
072  /**
073   * Constructs a new {@link LocalVariablesSorter}. <i>Subclasses must not use this constructor</i>.
074   * Instead, they must use the {@link #LocalVariablesSorter(int, int, String, MethodVisitor)}
075   * version.
076   *
077   * @param access access flags of the adapted method.
078   * @param descriptor the method's descriptor (see {@link Type}).
079   * @param methodVisitor the method visitor to which this adapter delegates calls.
080   * @throws IllegalStateException if a subclass calls this constructor.
081   */
082  public LocalVariablesSorter(
083      final int access, final String descriptor, final MethodVisitor methodVisitor) {
084    this(/* latest api = */ Opcodes.ASM9, access, descriptor, methodVisitor);
085    if (getClass() != LocalVariablesSorter.class) {
086      throw new IllegalStateException();
087    }
088  }
089
090  /**
091   * Constructs a new {@link LocalVariablesSorter}.
092   *
093   * @param api the ASM API version implemented by this visitor. Must be one of the {@code
094   *     ASM}<i>x</i> values in {@link Opcodes}.
095   * @param access access flags of the adapted method.
096   * @param descriptor the method's descriptor (see {@link Type}).
097   * @param methodVisitor the method visitor to which this adapter delegates calls.
098   */
099  protected LocalVariablesSorter(
100      final int api, final int access, final String descriptor, final MethodVisitor methodVisitor) {
101    super(api, methodVisitor);
102    nextLocal = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0;
103    for (Type argumentType : Type.getArgumentTypes(descriptor)) {
104      nextLocal += argumentType.getSize();
105    }
106    firstLocal = nextLocal;
107  }
108
109  @Override
110  public void visitVarInsn(final int opcode, final int varIndex) {
111    Type varType;
112    switch (opcode) {
113      case Opcodes.LLOAD:
114      case Opcodes.LSTORE:
115        varType = Type.LONG_TYPE;
116        break;
117      case Opcodes.DLOAD:
118      case Opcodes.DSTORE:
119        varType = Type.DOUBLE_TYPE;
120        break;
121      case Opcodes.FLOAD:
122      case Opcodes.FSTORE:
123        varType = Type.FLOAT_TYPE;
124        break;
125      case Opcodes.ILOAD:
126      case Opcodes.ISTORE:
127        varType = Type.INT_TYPE;
128        break;
129      case Opcodes.ALOAD:
130      case Opcodes.ASTORE:
131      case Opcodes.RET:
132        varType = OBJECT_TYPE;
133        break;
134      default:
135        throw new IllegalArgumentException("Invalid opcode " + opcode);
136    }
137    super.visitVarInsn(opcode, remap(varIndex, varType));
138  }
139
140  @Override
141  public void visitIincInsn(final int varIndex, final int increment) {
142    super.visitIincInsn(remap(varIndex, Type.INT_TYPE), increment);
143  }
144
145  @Override
146  public void visitMaxs(final int maxStack, final int maxLocals) {
147    super.visitMaxs(maxStack, nextLocal);
148  }
149
150  @Override
151  public void visitLocalVariable(
152      final String name,
153      final String descriptor,
154      final String signature,
155      final Label start,
156      final Label end,
157      final int index) {
158    int remappedIndex = remap(index, Type.getType(descriptor));
159    super.visitLocalVariable(name, descriptor, signature, start, end, remappedIndex);
160  }
161
162  @Override
163  public AnnotationVisitor visitLocalVariableAnnotation(
164      final int typeRef,
165      final TypePath typePath,
166      final Label[] start,
167      final Label[] end,
168      final int[] index,
169      final String descriptor,
170      final boolean visible) {
171    Type type = Type.getType(descriptor);
172    int[] remappedIndex = new int[index.length];
173    for (int i = 0; i < remappedIndex.length; ++i) {
174      remappedIndex[i] = remap(index[i], type);
175    }
176    return super.visitLocalVariableAnnotation(
177        typeRef, typePath, start, end, remappedIndex, descriptor, visible);
178  }
179
180  @Override
181  public void visitFrame(
182      final int type,
183      final int numLocal,
184      final Object[] local,
185      final int numStack,
186      final Object[] stack) {
187    if (type != Opcodes.F_NEW) { // Uncompressed frame.
188      throw new IllegalArgumentException(
189          "LocalVariablesSorter only accepts expanded frames (see ClassReader.EXPAND_FRAMES)");
190    }
191
192    // Create a copy of remappedLocals.
193    Object[] oldRemappedLocals = new Object[remappedLocalTypes.length];
194    System.arraycopy(remappedLocalTypes, 0, oldRemappedLocals, 0, oldRemappedLocals.length);
195
196    updateNewLocals(remappedLocalTypes);
197
198    // Copy the types from 'local' to 'remappedLocals'. 'remappedLocals' already contains the
199    // variables added with 'newLocal'.
200    int oldVar = 0; // Old local variable index.
201    for (int i = 0; i < numLocal; ++i) {
202      Object localType = local[i];
203      if (localType != Opcodes.TOP) {
204        Type varType = OBJECT_TYPE;
205        if (localType == Opcodes.INTEGER) {
206          varType = Type.INT_TYPE;
207        } else if (localType == Opcodes.FLOAT) {
208          varType = Type.FLOAT_TYPE;
209        } else if (localType == Opcodes.LONG) {
210          varType = Type.LONG_TYPE;
211        } else if (localType == Opcodes.DOUBLE) {
212          varType = Type.DOUBLE_TYPE;
213        } else if (localType instanceof String) {
214          varType = Type.getObjectType((String) localType);
215        }
216        setFrameLocal(remap(oldVar, varType), localType);
217      }
218      oldVar += localType == Opcodes.LONG || localType == Opcodes.DOUBLE ? 2 : 1;
219    }
220
221    // Remove TOP after long and double types as well as trailing TOPs.
222    oldVar = 0;
223    int newVar = 0;
224    int remappedNumLocal = 0;
225    while (oldVar < remappedLocalTypes.length) {
226      Object localType = remappedLocalTypes[oldVar];
227      oldVar += localType == Opcodes.LONG || localType == Opcodes.DOUBLE ? 2 : 1;
228      if (localType != null && localType != Opcodes.TOP) {
229        remappedLocalTypes[newVar++] = localType;
230        remappedNumLocal = newVar;
231      } else {
232        remappedLocalTypes[newVar++] = Opcodes.TOP;
233      }
234    }
235
236    // Visit the remapped frame.
237    super.visitFrame(type, remappedNumLocal, remappedLocalTypes, numStack, stack);
238
239    // Restore the original value of 'remappedLocals'.
240    remappedLocalTypes = oldRemappedLocals;
241  }
242
243  // -----------------------------------------------------------------------------------------------
244
245  /**
246   * Constructs a new local variable of the given type.
247   *
248   * @param type the type of the local variable to be created.
249   * @return the identifier of the newly created local variable.
250   */
251  public int newLocal(final Type type) {
252    Object localType;
253    switch (type.getSort()) {
254      case Type.BOOLEAN:
255      case Type.CHAR:
256      case Type.BYTE:
257      case Type.SHORT:
258      case Type.INT:
259        localType = Opcodes.INTEGER;
260        break;
261      case Type.FLOAT:
262        localType = Opcodes.FLOAT;
263        break;
264      case Type.LONG:
265        localType = Opcodes.LONG;
266        break;
267      case Type.DOUBLE:
268        localType = Opcodes.DOUBLE;
269        break;
270      case Type.ARRAY:
271        localType = type.getDescriptor();
272        break;
273      case Type.OBJECT:
274        localType = type.getInternalName();
275        break;
276      default:
277        throw new AssertionError();
278    }
279    int local = newLocalMapping(type);
280    setLocalType(local, type);
281    setFrameLocal(local, localType);
282    return local;
283  }
284
285  /**
286   * Notifies subclasses that a new stack map frame is being visited. The array argument contains
287   * the stack map frame types corresponding to the local variables added with {@link #newLocal}.
288   * This method can update these types in place for the stack map frame being visited. The default
289   * implementation of this method does nothing, i.e. a local variable added with {@link #newLocal}
290   * will have the same type in all stack map frames. But this behavior is not always the desired
291   * one, for instance if a local variable is added in the middle of a try/catch block: the frame
292   * for the exception handler should have a TOP type for this new local.
293   *
294   * @param newLocals the stack map frame types corresponding to the local variables added with
295   *     {@link #newLocal} (and null for the others). The format of this array is the same as in
296   *     {@link MethodVisitor#visitFrame}, except that long and double types use two slots. The
297   *     types for the current stack map frame must be updated in place in this array.
298   */
299  protected void updateNewLocals(final Object[] newLocals) {
300    // The default implementation does nothing.
301  }
302
303  /**
304   * Notifies subclasses that a local variable has been added or remapped. The default
305   * implementation of this method does nothing.
306   *
307   * @param local a local variable identifier, as returned by {@link #newLocal}.
308   * @param type the type of the value being stored in the local variable.
309   */
310  protected void setLocalType(final int local, final Type type) {
311    // The default implementation does nothing.
312  }
313
314  private void setFrameLocal(final int local, final Object type) {
315    int numLocals = remappedLocalTypes.length;
316    if (local >= numLocals) {
317      Object[] newRemappedLocalTypes = new Object[Math.max(2 * numLocals, local + 1)];
318      System.arraycopy(remappedLocalTypes, 0, newRemappedLocalTypes, 0, numLocals);
319      remappedLocalTypes = newRemappedLocalTypes;
320    }
321    remappedLocalTypes[local] = type;
322  }
323
324  private int remap(final int varIndex, final Type type) {
325    if (varIndex + type.getSize() <= firstLocal) {
326      return varIndex;
327    }
328    int key = 2 * varIndex + type.getSize() - 1;
329    int size = remappedVariableIndices.length;
330    if (key >= size) {
331      int[] newRemappedVariableIndices = new int[Math.max(2 * size, key + 1)];
332      System.arraycopy(remappedVariableIndices, 0, newRemappedVariableIndices, 0, size);
333      remappedVariableIndices = newRemappedVariableIndices;
334    }
335    int value = remappedVariableIndices[key];
336    if (value == 0) {
337      value = newLocalMapping(type);
338      setLocalType(value, type);
339      remappedVariableIndices[key] = value + 1;
340    } else {
341      value--;
342    }
343    return value;
344  }
345
346  protected int newLocalMapping(final Type type) {
347    int local = nextLocal;
348    nextLocal += type.getSize();
349    return local;
350  }
351}