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.*;
031
032/**
033 * A {@link MethodVisitor} that renumbers local variables in their order of appearance. This adapter
034 * allows one to easily add new local variables to a method. It may be used by inheriting from this
035 * class, but the preferred way of using it is via delegation: the next visitor in the chain can
036 * indeed add new locals when needed by calling {@link #newLocal} on this adapter (this requires a
037 * reference back to this {@link LocalVariablesSorter}).
038 *
039 * @author Chris Nokleberg
040 * @author Eugene Kuleshov
041 * @author Eric Bruneton
042 */
043public class LocalVariablesSorter extends MethodVisitor {
044
045  /** The type of the java.lang.Object class. */
046  private static final Type OBJECT_TYPE = Type.getObjectType("java/lang/Object");
047
048  /**
049   * The mapping from old to new local variable indices. A local variable at index i of size 1 is
050   * remapped to 'mapping[2*i]', while a local variable at index i of size 2 is remapped to
051   * 'mapping[2*i+1]'.
052   */
053  private int[] remappedVariableIndices = new int[40];
054
055  /**
056   * The local variable types after remapping. The format of this array is the same as in {@link
057   * MethodVisitor#visitFrame}, except that long and double types use two slots.
058   */
059  private Object[] remappedLocalTypes = new Object[20];
060
061  /** The index of the first local variable, after formal parameters. */
062  protected final int firstLocal;
063
064  /** The index of the next local variable to be created by {@link #newLocal}. */
065  protected int nextLocal;
066
067  /**
068   * Constructs a new {@link LocalVariablesSorter}. <i>Subclasses must not use this constructor</i>.
069   * Instead, they must use the {@link #LocalVariablesSorter(int, int, String, MethodVisitor)}
070   * version.
071   *
072   * @param access access flags of the adapted method.
073   * @param descriptor the method's descriptor (see {@link Type}).
074   * @param methodVisitor the method visitor to which this adapter delegates calls.
075   * @throws IllegalStateException if a subclass calls this constructor.
076   */
077  public LocalVariablesSorter(
078      final int access, final String descriptor, final MethodVisitor methodVisitor) {
079    this(/* latest api = */ Opcodes.ASM9, access, descriptor, methodVisitor);
080    if (getClass() != LocalVariablesSorter.class) {
081      throw new IllegalStateException();
082    }
083  }
084
085  /**
086   * Constructs a new {@link LocalVariablesSorter}.
087   *
088   * @param api the ASM API version implemented by this visitor. Must be one of the {@code
089   *     ASM}<i>x</i> values in {@link Opcodes}.
090   * @param access access flags of the adapted method.
091   * @param descriptor the method's descriptor (see {@link Type}).
092   * @param methodVisitor the method visitor to which this adapter delegates calls.
093   */
094  protected LocalVariablesSorter(
095      final int api, final int access, final String descriptor, final MethodVisitor methodVisitor) {
096    super(api, methodVisitor);
097    nextLocal = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0;
098    for (Type argumentType : Type.getArgumentTypes(descriptor)) {
099      nextLocal += argumentType.getSize();
100    }
101    firstLocal = nextLocal;
102  }
103
104  @Override
105  public void visitVarInsn(final int opcode, final int varIndex) {
106    Type varType;
107    switch (opcode) {
108      case Opcodes.LLOAD:
109      case Opcodes.LSTORE:
110        varType = Type.LONG_TYPE;
111        break;
112      case Opcodes.DLOAD:
113      case Opcodes.DSTORE:
114        varType = Type.DOUBLE_TYPE;
115        break;
116      case Opcodes.FLOAD:
117      case Opcodes.FSTORE:
118        varType = Type.FLOAT_TYPE;
119        break;
120      case Opcodes.ILOAD:
121      case Opcodes.ISTORE:
122        varType = Type.INT_TYPE;
123        break;
124      case Opcodes.ALOAD:
125      case Opcodes.ASTORE:
126      case Opcodes.RET:
127        varType = OBJECT_TYPE;
128        break;
129      default:
130        throw new IllegalArgumentException("Invalid opcode " + opcode);
131    }
132    super.visitVarInsn(opcode, remap(varIndex, varType));
133  }
134
135  @Override
136  public void visitIincInsn(final int varIndex, final int increment) {
137    super.visitIincInsn(remap(varIndex, Type.INT_TYPE), increment);
138  }
139
140  @Override
141  public void visitMaxs(final int maxStack, final int maxLocals) {
142    super.visitMaxs(maxStack, nextLocal);
143  }
144
145  @Override
146  public void visitLocalVariable(
147      final String name,
148      final String descriptor,
149      final String signature,
150      final Label start,
151      final Label end,
152      final int index) {
153    int remappedIndex = remap(index, Type.getType(descriptor));
154    super.visitLocalVariable(name, descriptor, signature, start, end, remappedIndex);
155  }
156
157  @Override
158  public AnnotationVisitor visitLocalVariableAnnotation(
159      final int typeRef,
160      final TypePath typePath,
161      final Label[] start,
162      final Label[] end,
163      final int[] index,
164      final String descriptor,
165      final boolean visible) {
166    Type type = Type.getType(descriptor);
167    int[] remappedIndex = new int[index.length];
168    for (int i = 0; i < remappedIndex.length; ++i) {
169      remappedIndex[i] = remap(index[i], type);
170    }
171    return super.visitLocalVariableAnnotation(
172        typeRef, typePath, start, end, remappedIndex, descriptor, visible);
173  }
174
175  @Override
176  public void visitFrame(
177      final int type,
178      final int numLocal,
179      final Object[] local,
180      final int numStack,
181      final Object[] stack) {
182    if (type != Opcodes.F_NEW) { // Uncompressed frame.
183      throw new IllegalArgumentException(
184          "LocalVariablesSorter only accepts expanded frames (see ClassReader.EXPAND_FRAMES)");
185    }
186
187    // Create a copy of remappedLocals.
188    Object[] oldRemappedLocals = new Object[remappedLocalTypes.length];
189    System.arraycopy(remappedLocalTypes, 0, oldRemappedLocals, 0, oldRemappedLocals.length);
190
191    updateNewLocals(remappedLocalTypes);
192
193    // Copy the types from 'local' to 'remappedLocals'. 'remappedLocals' already contains the
194    // variables added with 'newLocal'.
195    int oldVar = 0; // Old local variable index.
196    for (int i = 0; i < numLocal; ++i) {
197      Object localType = local[i];
198      if (localType != Opcodes.TOP) {
199        Type varType = OBJECT_TYPE;
200        if (localType == Opcodes.INTEGER) {
201          varType = Type.INT_TYPE;
202        } else if (localType == Opcodes.FLOAT) {
203          varType = Type.FLOAT_TYPE;
204        } else if (localType == Opcodes.LONG) {
205          varType = Type.LONG_TYPE;
206        } else if (localType == Opcodes.DOUBLE) {
207          varType = Type.DOUBLE_TYPE;
208        } else if (localType instanceof String) {
209          varType = Type.getObjectType((String) localType);
210        }
211        setFrameLocal(remap(oldVar, varType), localType);
212      }
213      oldVar += localType == Opcodes.LONG || localType == Opcodes.DOUBLE ? 2 : 1;
214    }
215
216    // Remove TOP after long and double types as well as trailing TOPs.
217    oldVar = 0;
218    int newVar = 0;
219    int remappedNumLocal = 0;
220    while (oldVar < remappedLocalTypes.length) {
221      Object localType = remappedLocalTypes[oldVar];
222      oldVar += localType == Opcodes.LONG || localType == Opcodes.DOUBLE ? 2 : 1;
223      if (localType != null && localType != Opcodes.TOP) {
224        remappedLocalTypes[newVar++] = localType;
225        remappedNumLocal = newVar;
226      } else {
227        remappedLocalTypes[newVar++] = Opcodes.TOP;
228      }
229    }
230
231    // Visit the remapped frame.
232    super.visitFrame(type, remappedNumLocal, remappedLocalTypes, numStack, stack);
233
234    // Restore the original value of 'remappedLocals'.
235    remappedLocalTypes = oldRemappedLocals;
236  }
237
238  // -----------------------------------------------------------------------------------------------
239
240  /**
241   * Constructs a new local variable of the given type.
242   *
243   * @param type the type of the local variable to be created.
244   * @return the identifier of the newly created local variable.
245   */
246  public int newLocal(final Type type) {
247    Object localType;
248    switch (type.getSort()) {
249      case Type.BOOLEAN:
250      case Type.CHAR:
251      case Type.BYTE:
252      case Type.SHORT:
253      case Type.INT:
254        localType = Opcodes.INTEGER;
255        break;
256      case Type.FLOAT:
257        localType = Opcodes.FLOAT;
258        break;
259      case Type.LONG:
260        localType = Opcodes.LONG;
261        break;
262      case Type.DOUBLE:
263        localType = Opcodes.DOUBLE;
264        break;
265      case Type.ARRAY:
266        localType = type.getDescriptor();
267        break;
268      case Type.OBJECT:
269        localType = type.getInternalName();
270        break;
271      default:
272        throw new AssertionError();
273    }
274    int local = newLocalMapping(type);
275    setLocalType(local, type);
276    setFrameLocal(local, localType);
277    return local;
278  }
279
280  /**
281   * Notifies subclasses that a new stack map frame is being visited. The array argument contains
282   * the stack map frame types corresponding to the local variables added with {@link #newLocal}.
283   * This method can update these types in place for the stack map frame being visited. The default
284   * implementation of this method does nothing, i.e. a local variable added with {@link #newLocal}
285   * will have the same type in all stack map frames. But this behavior is not always the desired
286   * one, for instance if a local variable is added in the middle of a try/catch block: the frame
287   * for the exception handler should have a TOP type for this new local.
288   *
289   * @param newLocals the stack map frame types corresponding to the local variables added with
290   *     {@link #newLocal} (and null for the others). The format of this array is the same as in
291   *     {@link MethodVisitor#visitFrame}, except that long and double types use two slots. The
292   *     types for the current stack map frame must be updated in place in this array.
293   */
294  protected void updateNewLocals(final Object[] newLocals) {
295    // The default implementation does nothing.
296  }
297
298  /**
299   * Notifies subclasses that a local variable has been added or remapped. The default
300   * implementation of this method does nothing.
301   *
302   * @param local a local variable identifier, as returned by {@link #newLocal}.
303   * @param type the type of the value being stored in the local variable.
304   */
305  protected void setLocalType(final int local, final Type type) {
306    // The default implementation does nothing.
307  }
308
309  private void setFrameLocal(final int local, final Object type) {
310    int numLocals = remappedLocalTypes.length;
311    if (local >= numLocals) {
312      Object[] newRemappedLocalTypes = new Object[Math.max(2 * numLocals, local + 1)];
313      System.arraycopy(remappedLocalTypes, 0, newRemappedLocalTypes, 0, numLocals);
314      remappedLocalTypes = newRemappedLocalTypes;
315    }
316    remappedLocalTypes[local] = type;
317  }
318
319  private int remap(final int varIndex, final Type type) {
320    if (varIndex + type.getSize() <= firstLocal) {
321      return varIndex;
322    }
323    int key = 2 * varIndex + type.getSize() - 1;
324    int size = remappedVariableIndices.length;
325    if (key >= size) {
326      int[] newRemappedVariableIndices = new int[Math.max(2 * size, key + 1)];
327      System.arraycopy(remappedVariableIndices, 0, newRemappedVariableIndices, 0, size);
328      remappedVariableIndices = newRemappedVariableIndices;
329    }
330    int value = remappedVariableIndices[key];
331    if (value == 0) {
332      value = newLocalMapping(type);
333      setLocalType(value, type);
334      remappedVariableIndices[key] = value + 1;
335    } else {
336      value--;
337    }
338    return value;
339  }
340
341  protected int newLocalMapping(final Type type) {
342    int local = nextLocal;
343    nextLocal += type.getSize();
344    return local;
345  }
346}