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.Handle;
033import io.ebean.enhance.asm.Label;
034import io.ebean.enhance.asm.MethodVisitor;
035import io.ebean.enhance.asm.Opcodes;
036import io.ebean.enhance.asm.Type;
037
038import java.util.ArrayList;
039import java.util.HashMap;
040import java.util.List;
041import java.util.Map;
042
043/**
044 * A {@link MethodVisitor} to insert before, after and around
045 * advices in methods and constructors.
046 * <p>
047 * The behavior for constructors is like this:
048 * <ol>
049 * 
050 * <li>as long as the INVOKESPECIAL for the object initialization has not been
051 * reached, every bytecode instruction is dispatched in the ctor code visitor</li>
052 * 
053 * <li>when this one is reached, it is only added in the ctor code visitor and a
054 * JP invoke is added</li>
055 * 
056 * <li>after that, only the other code visitor receives the instructions</li>
057 * 
058 * </ol>
059 * 
060 * @author Eugene Kuleshov
061 * @author Eric Bruneton
062 */
063public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes {
064
065    private static final Object THIS = new Object();
066
067    private static final Object OTHER = new Object();
068
069    protected int methodAccess;
070
071    protected String methodDesc;
072
073    private boolean constructor;
074
075    private boolean superInitialized;
076
077    private List<Object> stackFrame;
078
079    private Map<Label, List<Object>> branches;
080
081    /**
082     * Creates a new {@link AdviceAdapter}.
083     * 
084     * @param api
085     *            the ASM API version implemented by this visitor. Must be one
086     *            of {@link Opcodes#ASM4}, {@link Opcodes#ASM5} or {@link Opcodes#ASM6}.
087     * @param mv
088     *            the method visitor to which this adapter delegates calls.
089     * @param access
090     *            the method's access flags (see {@link Opcodes}).
091     * @param name
092     *            the method's name.
093     * @param desc
094     *            the method's descriptor (see {@link Type Type}).
095     */
096    protected AdviceAdapter(final int api, final MethodVisitor mv,
097            final int access, final String name, final String desc) {
098        super(api, mv, access, name, desc);
099        methodAccess = access;
100        methodDesc = desc;
101        constructor = "<init>".equals(name);
102    }
103
104    @Override
105    public void visitCode() {
106        mv.visitCode();
107        if (constructor) {
108            stackFrame = new ArrayList<Object>();
109            branches = new HashMap<Label, List<Object>>();
110        } else {
111            superInitialized = true;
112            onMethodEnter();
113        }
114    }
115
116    @Override
117    public void visitLabel(final Label label) {
118        mv.visitLabel(label);
119        if (constructor && branches != null) {
120            List<Object> frame = branches.get(label);
121            if (frame != null) {
122                stackFrame = frame;
123                branches.remove(label);
124            }
125        }
126    }
127
128    @Override
129    public void visitInsn(final int opcode) {
130        if (constructor) {
131            int s;
132            switch (opcode) {
133            case RETURN: // empty stack
134                onMethodExit(opcode);
135                break;
136            case IRETURN: // 1 before n/a after
137            case FRETURN: // 1 before n/a after
138            case ARETURN: // 1 before n/a after
139            case ATHROW: // 1 before n/a after
140                popValue();
141                onMethodExit(opcode);
142                break;
143            case LRETURN: // 2 before n/a after
144            case DRETURN: // 2 before n/a after
145                popValue();
146                popValue();
147                onMethodExit(opcode);
148                break;
149            case NOP:
150            case LALOAD: // remove 2 add 2
151            case DALOAD: // remove 2 add 2
152            case LNEG:
153            case DNEG:
154            case FNEG:
155            case INEG:
156            case L2D:
157            case D2L:
158            case F2I:
159            case I2B:
160            case I2C:
161            case I2S:
162            case I2F:
163            case ARRAYLENGTH:
164                break;
165            case ACONST_NULL:
166            case ICONST_M1:
167            case ICONST_0:
168            case ICONST_1:
169            case ICONST_2:
170            case ICONST_3:
171            case ICONST_4:
172            case ICONST_5:
173            case FCONST_0:
174            case FCONST_1:
175            case FCONST_2:
176            case F2L: // 1 before 2 after
177            case F2D:
178            case I2L:
179            case I2D:
180                pushValue(OTHER);
181                break;
182            case LCONST_0:
183            case LCONST_1:
184            case DCONST_0:
185            case DCONST_1:
186                pushValue(OTHER);
187                pushValue(OTHER);
188                break;
189            case IALOAD: // remove 2 add 1
190            case FALOAD: // remove 2 add 1
191            case AALOAD: // remove 2 add 1
192            case BALOAD: // remove 2 add 1
193            case CALOAD: // remove 2 add 1
194            case SALOAD: // remove 2 add 1
195            case POP:
196            case IADD:
197            case FADD:
198            case ISUB:
199            case LSHL: // 3 before 2 after
200            case LSHR: // 3 before 2 after
201            case LUSHR: // 3 before 2 after
202            case L2I: // 2 before 1 after
203            case L2F: // 2 before 1 after
204            case D2I: // 2 before 1 after
205            case D2F: // 2 before 1 after
206            case FSUB:
207            case FMUL:
208            case FDIV:
209            case FREM:
210            case FCMPL: // 2 before 1 after
211            case FCMPG: // 2 before 1 after
212            case IMUL:
213            case IDIV:
214            case IREM:
215            case ISHL:
216            case ISHR:
217            case IUSHR:
218            case IAND:
219            case IOR:
220            case IXOR:
221            case MONITORENTER:
222            case MONITOREXIT:
223                popValue();
224                break;
225            case POP2:
226            case LSUB:
227            case LMUL:
228            case LDIV:
229            case LREM:
230            case LADD:
231            case LAND:
232            case LOR:
233            case LXOR:
234            case DADD:
235            case DMUL:
236            case DSUB:
237            case DDIV:
238            case DREM:
239                popValue();
240                popValue();
241                break;
242            case IASTORE:
243            case FASTORE:
244            case AASTORE:
245            case BASTORE:
246            case CASTORE:
247            case SASTORE:
248            case LCMP: // 4 before 1 after
249            case DCMPL:
250            case DCMPG:
251                popValue();
252                popValue();
253                popValue();
254                break;
255            case LASTORE:
256            case DASTORE:
257                popValue();
258                popValue();
259                popValue();
260                popValue();
261                break;
262            case DUP:
263                pushValue(peekValue());
264                break;
265            case DUP_X1:
266                s = stackFrame.size();
267                stackFrame.add(s - 2, stackFrame.get(s - 1));
268                break;
269            case DUP_X2:
270                s = stackFrame.size();
271                stackFrame.add(s - 3, stackFrame.get(s - 1));
272                break;
273            case DUP2:
274                s = stackFrame.size();
275                stackFrame.add(s - 2, stackFrame.get(s - 1));
276                stackFrame.add(s - 2, stackFrame.get(s - 1));
277                break;
278            case DUP2_X1:
279                s = stackFrame.size();
280                stackFrame.add(s - 3, stackFrame.get(s - 1));
281                stackFrame.add(s - 3, stackFrame.get(s - 1));
282                break;
283            case DUP2_X2:
284                s = stackFrame.size();
285                stackFrame.add(s - 4, stackFrame.get(s - 1));
286                stackFrame.add(s - 4, stackFrame.get(s - 1));
287                break;
288            case SWAP:
289                s = stackFrame.size();
290                stackFrame.add(s - 2, stackFrame.get(s - 1));
291                stackFrame.remove(s);
292                break;
293            }
294        } else {
295            switch (opcode) {
296            case RETURN:
297            case IRETURN:
298            case FRETURN:
299            case ARETURN:
300            case LRETURN:
301            case DRETURN:
302            case ATHROW:
303                onMethodExit(opcode);
304                break;
305            }
306        }
307        mv.visitInsn(opcode);
308    }
309
310    @Override
311    public void visitVarInsn(final int opcode, final int var) {
312        super.visitVarInsn(opcode, var);
313        if (constructor) {
314            switch (opcode) {
315            case ILOAD:
316            case FLOAD:
317                pushValue(OTHER);
318                break;
319            case LLOAD:
320            case DLOAD:
321                pushValue(OTHER);
322                pushValue(OTHER);
323                break;
324            case ALOAD:
325                pushValue(var == 0 ? THIS : OTHER);
326                break;
327            case ASTORE:
328            case ISTORE:
329            case FSTORE:
330                popValue();
331                break;
332            case LSTORE:
333            case DSTORE:
334                popValue();
335                popValue();
336                break;
337            }
338        }
339    }
340
341    @Override
342    public void visitFieldInsn(final int opcode, final String owner,
343            final String name, final String desc) {
344        mv.visitFieldInsn(opcode, owner, name, desc);
345        if (constructor) {
346            char c = desc.charAt(0);
347            boolean longOrDouble = c == 'J' || c == 'D';
348            switch (opcode) {
349            case GETSTATIC:
350                pushValue(OTHER);
351                if (longOrDouble) {
352                    pushValue(OTHER);
353                }
354                break;
355            case PUTSTATIC:
356                popValue();
357                if (longOrDouble) {
358                    popValue();
359                }
360                break;
361            case PUTFIELD:
362                popValue();
363                popValue();
364                if (longOrDouble) {
365                    popValue();
366                }
367                break;
368            // case GETFIELD:
369            default:
370                if (longOrDouble) {
371                    pushValue(OTHER);
372                }
373            }
374        }
375    }
376
377    @Override
378    public void visitIntInsn(final int opcode, final int operand) {
379        mv.visitIntInsn(opcode, operand);
380        if (constructor && opcode != NEWARRAY) {
381            pushValue(OTHER);
382        }
383    }
384
385    @Override
386    public void visitLdcInsn(final Object cst) {
387        mv.visitLdcInsn(cst);
388        if (constructor) {
389            pushValue(OTHER);
390            if (cst instanceof Double || cst instanceof Long) {
391                pushValue(OTHER);
392            }
393        }
394    }
395
396    @Override
397    public void visitMultiANewArrayInsn(final String desc, final int dims) {
398        mv.visitMultiANewArrayInsn(desc, dims);
399        if (constructor) {
400            for (int i = 0; i < dims; i++) {
401                popValue();
402            }
403            pushValue(OTHER);
404        }
405    }
406
407    @Override
408    public void visitTypeInsn(final int opcode, final String type) {
409        mv.visitTypeInsn(opcode, type);
410        // ANEWARRAY, CHECKCAST or INSTANCEOF don't change stack
411        if (constructor && opcode == NEW) {
412            pushValue(OTHER);
413        }
414    }
415
416    @Deprecated
417    @Override
418    public void visitMethodInsn(final int opcode, final String owner,
419            final String name, final String desc) {
420        if (api >= Opcodes.ASM5) {
421            super.visitMethodInsn(opcode, owner, name, desc);
422            return;
423        }
424        doVisitMethodInsn(opcode, owner, name, desc,
425                opcode == Opcodes.INVOKEINTERFACE);
426    }
427
428    @Override
429    public void visitMethodInsn(final int opcode, final String owner,
430            final String name, final String desc, final boolean itf) {
431        if (api < Opcodes.ASM5) {
432            super.visitMethodInsn(opcode, owner, name, desc, itf);
433            return;
434        }
435        doVisitMethodInsn(opcode, owner, name, desc, itf);
436    }
437
438    private void doVisitMethodInsn(int opcode, final String owner,
439            final String name, final String desc, final boolean itf) {
440        mv.visitMethodInsn(opcode, owner, name, desc, itf);
441        if (constructor) {
442            Type[] types = Type.getArgumentTypes(desc);
443            for (int i = 0; i < types.length; i++) {
444                popValue();
445                if (types[i].getSize() == 2) {
446                    popValue();
447                }
448            }
449            switch (opcode) {
450            // case INVOKESTATIC:
451            // break;
452            case INVOKEINTERFACE:
453            case INVOKEVIRTUAL:
454                popValue(); // objectref
455                break;
456            case INVOKESPECIAL:
457                Object type = popValue(); // objectref
458                if (type == THIS && !superInitialized) {
459                    onMethodEnter();
460                    superInitialized = true;
461                    // once super has been initialized it is no longer
462                    // necessary to keep track of stack state
463                    constructor = false;
464                }
465                break;
466            }
467
468            Type returnType = Type.getReturnType(desc);
469            if (returnType != Type.VOID_TYPE) {
470                pushValue(OTHER);
471                if (returnType.getSize() == 2) {
472                    pushValue(OTHER);
473                }
474            }
475        }
476    }
477
478    @Override
479    public void visitInvokeDynamicInsn(String name, String desc, Handle bsm,
480            Object... bsmArgs) {
481        mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
482        if (constructor) {
483            Type[] types = Type.getArgumentTypes(desc);
484            for (int i = 0; i < types.length; i++) {
485                popValue();
486                if (types[i].getSize() == 2) {
487                    popValue();
488                }
489            }
490
491            Type returnType = Type.getReturnType(desc);
492            if (returnType != Type.VOID_TYPE) {
493                pushValue(OTHER);
494                if (returnType.getSize() == 2) {
495                    pushValue(OTHER);
496                }
497            }
498        }
499    }
500
501    @Override
502    public void visitJumpInsn(final int opcode, final Label label) {
503        mv.visitJumpInsn(opcode, label);
504        if (constructor) {
505            switch (opcode) {
506            case IFEQ:
507            case IFNE:
508            case IFLT:
509            case IFGE:
510            case IFGT:
511            case IFLE:
512            case IFNULL:
513            case IFNONNULL:
514                popValue();
515                break;
516            case IF_ICMPEQ:
517            case IF_ICMPNE:
518            case IF_ICMPLT:
519            case IF_ICMPGE:
520            case IF_ICMPGT:
521            case IF_ICMPLE:
522            case IF_ACMPEQ:
523            case IF_ACMPNE:
524                popValue();
525                popValue();
526                break;
527            case JSR:
528                pushValue(OTHER);
529                break;
530            }
531            addBranch(label);
532        }
533    }
534
535    @Override
536    public void visitLookupSwitchInsn(final Label dflt, final int[] keys,
537                                      final Label[] labels) {
538        mv.visitLookupSwitchInsn(dflt, keys, labels);
539        if (constructor) {
540            popValue();
541            addBranches(dflt, labels);
542        }
543    }
544
545    @Override
546    public void visitTableSwitchInsn(final int min, final int max,
547                                     final Label dflt, final Label... labels) {
548        mv.visitTableSwitchInsn(min, max, dflt, labels);
549        if (constructor) {
550            popValue();
551            addBranches(dflt, labels);
552        }
553    }
554
555    @Override
556    public void visitTryCatchBlock(Label start, Label end, Label handler,
557                                   String type) {
558        super.visitTryCatchBlock(start, end, handler, type);
559        if (constructor && !branches.containsKey(handler)) {
560            List<Object> stackFrame = new ArrayList<Object>();
561            stackFrame.add(OTHER);
562            branches.put(handler, stackFrame);
563        }
564    }
565
566    private void addBranches(final Label dflt, final Label[] labels) {
567        addBranch(dflt);
568        for (int i = 0; i < labels.length; i++) {
569            addBranch(labels[i]);
570        }
571    }
572
573    private void addBranch(final Label label) {
574        if (branches.containsKey(label)) {
575            return;
576        }
577        branches.put(label, new ArrayList<Object>(stackFrame));
578    }
579
580    private Object popValue() {
581        return stackFrame.remove(stackFrame.size() - 1);
582    }
583
584    private Object peekValue() {
585        return stackFrame.get(stackFrame.size() - 1);
586    }
587
588    private void pushValue(final Object o) {
589        stackFrame.add(o);
590    }
591
592    /**
593     * Called at the beginning of the method or after super class call in
594     * the constructor. <br>
595     * <br>
596     * 
597     * <i>Custom code can use or change all the local variables, but should not
598     * change state of the stack.</i>
599     */
600    protected void onMethodEnter() {
601    }
602
603    /**
604     * Called before explicit exit from the method using either return or throw.
605     * Top element on the stack contains the return value or exception instance.
606     * For example:
607     * 
608     * <pre>
609     *   public void onMethodExit(int opcode) {
610     *     if(opcode==RETURN) {
611     *         visitInsn(ACONST_NULL);
612     *     } else if(opcode==ARETURN || opcode==ATHROW) {
613     *         dup();
614     *     } else {
615     *         if(opcode==LRETURN || opcode==DRETURN) {
616     *             dup2();
617     *         } else {
618     *             dup();
619     *         }
620     *         box(Type.getReturnType(this.methodDesc));
621     *     }
622     *     visitIntInsn(SIPUSH, opcode);
623     *     visitMethodInsn(INVOKESTATIC, owner, "onExit", "(Ljava/lang/Object;I)V");
624     *   }
625     * 
626     *   // an actual call back method
627     *   public static void onExit(Object param, int opcode) {
628     *     ...
629     * </pre>
630     * 
631     * <br>
632     * <br>
633     * 
634     * <i>Custom code can use or change all the local variables, but should not
635     * change state of the stack.</i>
636     * 
637     * @param opcode
638     *            one of the RETURN, IRETURN, FRETURN, ARETURN, LRETURN, DRETURN
639     *            or ATHROW
640     * 
641     */
642    protected void onMethodExit(int opcode) {
643    }
644
645    // TODO onException, onMethodCall
646}