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