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 {@link
094   *     Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6}, {@link Opcodes#ASM7}, {@link
095   *     Opcodes#ASM8} or {@link Opcodes#ASM9}.
096   * @param access access flags of the adapted method.
097   * @param descriptor the method's descriptor (see {@link Type}).
098   * @param methodVisitor the method visitor to which this adapter delegates calls.
099   */
100  protected LocalVariablesSorter(
101      final int api, final int access, final String descriptor, final MethodVisitor methodVisitor) {
102    super(api, methodVisitor);
103    nextLocal = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0;
104    for (Type argumentType : Type.getArgumentTypes(descriptor)) {
105      nextLocal += argumentType.getSize();
106    }
107    firstLocal = nextLocal;
108  }
109
110  @Override
111  public void visitVarInsn(final int opcode, final int var) {
112    Type varType;
113    switch (opcode) {
114      case Opcodes.LLOAD:
115      case Opcodes.LSTORE:
116        varType = Type.LONG_TYPE;
117        break;
118      case Opcodes.DLOAD:
119      case Opcodes.DSTORE:
120        varType = Type.DOUBLE_TYPE;
121        break;
122      case Opcodes.FLOAD:
123      case Opcodes.FSTORE:
124        varType = Type.FLOAT_TYPE;
125        break;
126      case Opcodes.ILOAD:
127      case Opcodes.ISTORE:
128        varType = Type.INT_TYPE;
129        break;
130      case Opcodes.ALOAD:
131      case Opcodes.ASTORE:
132      case Opcodes.RET:
133        varType = OBJECT_TYPE;
134        break;
135      default:
136        throw new IllegalArgumentException("Invalid opcode " + opcode);
137    }
138    super.visitVarInsn(opcode, remap(var, varType));
139  }
140
141  @Override
142  public void visitIincInsn(final int var, final int increment) {
143    super.visitIincInsn(remap(var, Type.INT_TYPE), increment);
144  }
145
146  @Override
147  public void visitMaxs(final int maxStack, final int maxLocals) {
148    super.visitMaxs(maxStack, nextLocal);
149  }
150
151  @Override
152  public void visitLocalVariable(
153      final String name,
154      final String descriptor,
155      final String signature,
156      final Label start,
157      final Label end,
158      final int index) {
159    int remappedIndex = remap(index, Type.getType(descriptor));
160    super.visitLocalVariable(name, descriptor, signature, start, end, remappedIndex);
161  }
162
163  @Override
164  public AnnotationVisitor visitLocalVariableAnnotation(
165      final int typeRef,
166      final TypePath typePath,
167      final Label[] start,
168      final Label[] end,
169      final int[] index,
170      final String descriptor,
171      final boolean visible) {
172    Type type = Type.getType(descriptor);
173    int[] remappedIndex = new int[index.length];
174    for (int i = 0; i < remappedIndex.length; ++i) {
175      remappedIndex[i] = remap(index[i], type);
176    }
177    return super.visitLocalVariableAnnotation(
178        typeRef, typePath, start, end, remappedIndex, descriptor, visible);
179  }
180
181  @Override
182  public void visitFrame(
183      final int type,
184      final int numLocal,
185      final Object[] local,
186      final int numStack,
187      final Object[] stack) {
188    if (type != Opcodes.F_NEW) { // Uncompressed frame.
189      throw new IllegalArgumentException(
190          "LocalVariablesSorter only accepts expanded frames (see ClassReader.EXPAND_FRAMES)");
191    }
192
193    // Create a copy of remappedLocals.
194    Object[] oldRemappedLocals = new Object[remappedLocalTypes.length];
195    System.arraycopy(remappedLocalTypes, 0, oldRemappedLocals, 0, oldRemappedLocals.length);
196
197    updateNewLocals(remappedLocalTypes);
198
199    // Copy the types from 'local' to 'remappedLocals'. 'remappedLocals' already contains the
200    // variables added with 'newLocal'.
201    int oldVar = 0; // Old local variable index.
202    for (int i = 0; i < numLocal; ++i) {
203      Object localType = local[i];
204      if (localType != Opcodes.TOP) {
205        Type varType = OBJECT_TYPE;
206        if (localType == Opcodes.INTEGER) {
207          varType = Type.INT_TYPE;
208        } else if (localType == Opcodes.FLOAT) {
209          varType = Type.FLOAT_TYPE;
210        } else if (localType == Opcodes.LONG) {
211          varType = Type.LONG_TYPE;
212        } else if (localType == Opcodes.DOUBLE) {
213          varType = Type.DOUBLE_TYPE;
214        } else if (localType instanceof String) {
215          varType = Type.getObjectType((String) localType);
216        }
217        setFrameLocal(remap(oldVar, varType), localType);
218      }
219      oldVar += localType == Opcodes.LONG || localType == Opcodes.DOUBLE ? 2 : 1;
220    }
221
222    // Remove TOP after long and double types as well as trailing TOPs.
223    oldVar = 0;
224    int newVar = 0;
225    int remappedNumLocal = 0;
226    while (oldVar < remappedLocalTypes.length) {
227      Object localType = remappedLocalTypes[oldVar];
228      oldVar += localType == Opcodes.LONG || localType == Opcodes.DOUBLE ? 2 : 1;
229      if (localType != null && localType != Opcodes.TOP) {
230        remappedLocalTypes[newVar++] = localType;
231        remappedNumLocal = newVar;
232      } else {
233        remappedLocalTypes[newVar++] = Opcodes.TOP;
234      }
235    }
236
237    // Visit the remapped frame.
238    super.visitFrame(type, remappedNumLocal, remappedLocalTypes, numStack, stack);
239
240    // Restore the original value of 'remappedLocals'.
241    remappedLocalTypes = oldRemappedLocals;
242  }
243
244  // -----------------------------------------------------------------------------------------------
245
246  /**
247   * Constructs a new local variable of the given type.
248   *
249   * @param type the type of the local variable to be created.
250   * @return the identifier of the newly created local variable.
251   */
252  public int newLocal(final Type type) {
253    Object localType;
254    switch (type.getSort()) {
255      case Type.BOOLEAN:
256      case Type.CHAR:
257      case Type.BYTE:
258      case Type.SHORT:
259      case Type.INT:
260        localType = Opcodes.INTEGER;
261        break;
262      case Type.FLOAT:
263        localType = Opcodes.FLOAT;
264        break;
265      case Type.LONG:
266        localType = Opcodes.LONG;
267        break;
268      case Type.DOUBLE:
269        localType = Opcodes.DOUBLE;
270        break;
271      case Type.ARRAY:
272        localType = type.getDescriptor();
273        break;
274      case Type.OBJECT:
275        localType = type.getInternalName();
276        break;
277      default:
278        throw new AssertionError();
279    }
280    int local = newLocalMapping(type);
281    setLocalType(local, type);
282    setFrameLocal(local, localType);
283    return local;
284  }
285
286  /**
287   * Notifies subclasses that a new stack map frame is being visited. The array argument contains
288   * the stack map frame types corresponding to the local variables added with {@link #newLocal}.
289   * This method can update these types in place for the stack map frame being visited. The default
290   * implementation of this method does nothing, i.e. a local variable added with {@link #newLocal}
291   * will have the same type in all stack map frames. But this behavior is not always the desired
292   * one, for instance if a local variable is added in the middle of a try/catch block: the frame
293   * for the exception handler should have a TOP type for this new local.
294   *
295   * @param newLocals the stack map frame types corresponding to the local variables added with
296   *     {@link #newLocal} (and null for the others). The format of this array is the same as in
297   *     {@link MethodVisitor#visitFrame}, except that long and double types use two slots. The
298   *     types for the current stack map frame must be updated in place in this array.
299   */
300  protected void updateNewLocals(final Object[] newLocals) {
301    // The default implementation does nothing.
302  }
303
304  /**
305   * Notifies subclasses that a local variable has been added or remapped. The default
306   * implementation of this method does nothing.
307   *
308   * @param local a local variable identifier, as returned by {@link #newLocal}.
309   * @param type the type of the value being stored in the local variable.
310   */
311  protected void setLocalType(final int local, final Type type) {
312    // The default implementation does nothing.
313  }
314
315  private void setFrameLocal(final int local, final Object type) {
316    int numLocals = remappedLocalTypes.length;
317    if (local >= numLocals) {
318      Object[] newRemappedLocalTypes = new Object[Math.max(2 * numLocals, local + 1)];
319      System.arraycopy(remappedLocalTypes, 0, newRemappedLocalTypes, 0, numLocals);
320      remappedLocalTypes = newRemappedLocalTypes;
321    }
322    remappedLocalTypes[local] = type;
323  }
324
325  private int remap(final int var, final Type type) {
326    if (var + type.getSize() <= firstLocal) {
327      return var;
328    }
329    int key = 2 * var + type.getSize() - 1;
330    int size = remappedVariableIndices.length;
331    if (key >= size) {
332      int[] newRemappedVariableIndices = new int[Math.max(2 * size, key + 1)];
333      System.arraycopy(remappedVariableIndices, 0, newRemappedVariableIndices, 0, size);
334      remappedVariableIndices = newRemappedVariableIndices;
335    }
336    int value = remappedVariableIndices[key];
337    if (value == 0) {
338      value = newLocalMapping(type);
339      setLocalType(value, type);
340      remappedVariableIndices[key] = value + 1;
341    } else {
342      value--;
343    }
344    return value;
345  }
346
347  protected int newLocalMapping(final Type type) {
348    int local = nextLocal;
349    nextLocal += type.getSize();
350    return local;
351  }
352}