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