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