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