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.ConstantDynamic;
031import io.ebean.enhance.asm.Handle;
032import io.ebean.enhance.asm.Label;
033import io.ebean.enhance.asm.MethodVisitor;
034import io.ebean.enhance.asm.Opcodes;
035import io.ebean.enhance.asm.Type;
036
037import java.util.ArrayList;
038import java.util.HashMap;
039import java.util.List;
040import java.util.Map;
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 {@link
103   *     Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
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          break;
159        case ATHROW: // 1 before n/a after
160          popValue();
161          onMethodExit(opcode);
162          break;
163        case NOP:
164        case LALOAD: // remove 2 add 2
165        case DALOAD: // remove 2 add 2
166        case LNEG:
167        case DNEG:
168        case FNEG:
169        case INEG:
170        case L2D:
171        case D2L:
172        case F2I:
173        case I2B:
174        case I2C:
175        case I2S:
176        case I2F:
177        case ARRAYLENGTH:
178          break;
179        case ACONST_NULL:
180        case ICONST_M1:
181        case ICONST_0:
182        case ICONST_1:
183        case ICONST_2:
184        case ICONST_3:
185        case ICONST_4:
186        case ICONST_5:
187        case FCONST_0:
188        case FCONST_1:
189        case FCONST_2:
190        case F2L: // 1 before 2 after
191        case F2D:
192        case I2L:
193        case I2D:
194          pushValue(OTHER);
195          break;
196        case LCONST_0:
197        case LCONST_1:
198        case DCONST_0:
199        case DCONST_1:
200          pushValue(OTHER);
201          pushValue(OTHER);
202          break;
203        case IALOAD: // remove 2 add 1
204        case FALOAD: // remove 2 add 1
205        case AALOAD: // remove 2 add 1
206        case BALOAD: // remove 2 add 1
207        case CALOAD: // remove 2 add 1
208        case SALOAD: // remove 2 add 1
209        case POP:
210        case IADD:
211        case FADD:
212        case ISUB:
213        case LSHL: // 3 before 2 after
214        case LSHR: // 3 before 2 after
215        case LUSHR: // 3 before 2 after
216        case L2I: // 2 before 1 after
217        case L2F: // 2 before 1 after
218        case D2I: // 2 before 1 after
219        case D2F: // 2 before 1 after
220        case FSUB:
221        case FMUL:
222        case FDIV:
223        case FREM:
224        case FCMPL: // 2 before 1 after
225        case FCMPG: // 2 before 1 after
226        case IMUL:
227        case IDIV:
228        case IREM:
229        case ISHL:
230        case ISHR:
231        case IUSHR:
232        case IAND:
233        case IOR:
234        case IXOR:
235        case MONITORENTER:
236        case MONITOREXIT:
237          popValue();
238          break;
239        case POP2:
240        case LSUB:
241        case LMUL:
242        case LDIV:
243        case LREM:
244        case LADD:
245        case LAND:
246        case LOR:
247        case LXOR:
248        case DADD:
249        case DMUL:
250        case DSUB:
251        case DDIV:
252        case DREM:
253          popValue();
254          popValue();
255          break;
256        case IASTORE:
257        case FASTORE:
258        case AASTORE:
259        case BASTORE:
260        case CASTORE:
261        case SASTORE:
262        case LCMP: // 4 before 1 after
263        case DCMPL:
264        case DCMPG:
265          popValue();
266          popValue();
267          popValue();
268          break;
269        case LASTORE:
270        case DASTORE:
271          popValue();
272          popValue();
273          popValue();
274          popValue();
275          break;
276        case DUP:
277          pushValue(peekValue());
278          break;
279        case DUP_X1:
280          stackSize = stackFrame.size();
281          stackFrame.add(stackSize - 2, stackFrame.get(stackSize - 1));
282          break;
283        case DUP_X2:
284          stackSize = stackFrame.size();
285          stackFrame.add(stackSize - 3, stackFrame.get(stackSize - 1));
286          break;
287        case DUP2:
288          stackSize = stackFrame.size();
289          stackFrame.add(stackSize - 2, stackFrame.get(stackSize - 1));
290          stackFrame.add(stackSize - 2, stackFrame.get(stackSize - 1));
291          break;
292        case DUP2_X1:
293          stackSize = stackFrame.size();
294          stackFrame.add(stackSize - 3, stackFrame.get(stackSize - 1));
295          stackFrame.add(stackSize - 3, stackFrame.get(stackSize - 1));
296          break;
297        case DUP2_X2:
298          stackSize = stackFrame.size();
299          stackFrame.add(stackSize - 4, stackFrame.get(stackSize - 1));
300          stackFrame.add(stackSize - 4, stackFrame.get(stackSize - 1));
301          break;
302        case SWAP:
303          stackSize = stackFrame.size();
304          stackFrame.add(stackSize - 2, stackFrame.get(stackSize - 1));
305          stackFrame.remove(stackSize);
306          break;
307        default:
308          throw new IllegalArgumentException(INVALID_OPCODE + opcode);
309      }
310    } else {
311      switch (opcode) {
312        case RETURN:
313        case IRETURN:
314        case FRETURN:
315        case ARETURN:
316        case LRETURN:
317        case DRETURN:
318        case ATHROW:
319          onMethodExit(opcode);
320          break;
321        default:
322          break;
323      }
324    }
325    super.visitInsn(opcode);
326  }
327
328  @Override
329  public void visitVarInsn(final int opcode, final int var) {
330    super.visitVarInsn(opcode, var);
331    if (isConstructor && !superClassConstructorCalled) {
332      switch (opcode) {
333        case ILOAD:
334        case FLOAD:
335          pushValue(OTHER);
336          break;
337        case LLOAD:
338        case DLOAD:
339          pushValue(OTHER);
340          pushValue(OTHER);
341          break;
342        case ALOAD:
343          pushValue(var == 0 ? UNINITIALIZED_THIS : OTHER);
344          break;
345        case ASTORE:
346        case ISTORE:
347        case FSTORE:
348          popValue();
349          break;
350        case LSTORE:
351        case DSTORE:
352          popValue();
353          popValue();
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, descriptor);
456  }
457
458  private void doVisitMethodInsn(final int opcode, 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 && !superClassConstructorCalled) {
474            superClassConstructorCalled = true;
475            onMethodEnter();
476          }
477          break;
478        default:
479          break;
480      }
481
482      Type returnType = Type.getReturnType(descriptor);
483      if (returnType != Type.VOID_TYPE) {
484        pushValue(OTHER);
485        if (returnType.getSize() == 2) {
486          pushValue(OTHER);
487        }
488      }
489    }
490  }
491
492  @Override
493  public void visitInvokeDynamicInsn(
494      final String name,
495      final String descriptor,
496      final Handle bootstrapMethodHandle,
497      final Object... bootstrapMethodArguments) {
498    super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
499    doVisitMethodInsn(Opcodes.INVOKEDYNAMIC, descriptor);
500  }
501
502  @Override
503  public void visitJumpInsn(final int opcode, final Label label) {
504    super.visitJumpInsn(opcode, label);
505    if (isConstructor && !superClassConstructorCalled) {
506      switch (opcode) {
507        case IFEQ:
508        case IFNE:
509        case IFLT:
510        case IFGE:
511        case IFGT:
512        case IFLE:
513        case IFNULL:
514        case IFNONNULL:
515          popValue();
516          break;
517        case IF_ICMPEQ:
518        case IF_ICMPNE:
519        case IF_ICMPLT:
520        case IF_ICMPGE:
521        case IF_ICMPGT:
522        case IF_ICMPLE:
523        case IF_ACMPEQ:
524        case IF_ACMPNE:
525          popValue();
526          popValue();
527          break;
528        case JSR:
529          pushValue(OTHER);
530          break;
531        default:
532          break;
533      }
534      addForwardJump(label);
535    }
536  }
537
538  @Override
539  public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
540    super.visitLookupSwitchInsn(dflt, keys, labels);
541    if (isConstructor && !superClassConstructorCalled) {
542      popValue();
543      addForwardJumps(dflt, labels);
544    }
545  }
546
547  @Override
548  public void visitTableSwitchInsn(
549    final int min, final int max, final Label dflt, final Label... labels) {
550    super.visitTableSwitchInsn(min, max, dflt, labels);
551    if (isConstructor && !superClassConstructorCalled) {
552      popValue();
553      addForwardJumps(dflt, labels);
554    }
555  }
556
557  @Override
558  public void visitTryCatchBlock(
559    final Label start, final Label end, final Label handler, final String type) {
560    super.visitTryCatchBlock(start, end, handler, type);
561    // By definition of 'forwardJumpStackFrames', 'handler' should be pushed only if there is an
562    // instruction between 'start' and 'end' at which the super class constructor is not yet
563    // called. Unfortunately, try catch blocks must be visited before their labels, so we have no
564    // way to know this at this point. Instead, we suppose that the super class constructor has not
565    // been called at the start of *any* exception handler. If this is wrong, normally there should
566    // not be a second super class constructor call in the exception handler (an object can't be
567    // initialized twice), so this is not issue (in the sense that there is no risk to emit a wrong
568    // 'onMethodEnter').
569    if (isConstructor && !forwardJumpStackFrames.containsKey(handler)) {
570      List<Object> handlerStackFrame = new ArrayList<>();
571      handlerStackFrame.add(OTHER);
572      forwardJumpStackFrames.put(handler, handlerStackFrame);
573    }
574  }
575
576  private void addForwardJumps(final Label dflt, final Label[] labels) {
577    addForwardJump(dflt);
578    for (Label label : labels) {
579      addForwardJump(label);
580    }
581  }
582
583  private void addForwardJump(final Label label) {
584    if (forwardJumpStackFrames.containsKey(label)) {
585      return;
586    }
587    forwardJumpStackFrames.put(label, new ArrayList<>(stackFrame));
588  }
589
590  private Object popValue() {
591    return stackFrame.remove(stackFrame.size() - 1);
592  }
593
594  private Object peekValue() {
595    return stackFrame.get(stackFrame.size() - 1);
596  }
597
598  private void pushValue(final Object value) {
599    stackFrame.add(value);
600  }
601
602  /**
603   * Generates the "before" advice for the visited method. The default implementation of this method
604   * does nothing. Subclasses can use or change all the local variables, but should not change state
605   * of the stack. This method is called at the beginning of the method or after super class
606   * constructor has been called (in constructors).
607   */
608  protected void onMethodEnter() {}
609
610  /**
611   * Generates the "after" advice for the visited method. The default implementation of this method
612   * does nothing. Subclasses can use or change all the local variables, but should not change state
613   * of the stack. This method is called at the end of the method, just before return and athrow
614   * instructions. The top element on the stack contains the return value or the exception instance.
615   * For example:
616   *
617   * <pre>
618   * public void onMethodExit(final int opcode) {
619   *   if (opcode == RETURN) {
620   *     visitInsn(ACONST_NULL);
621   *   } else if (opcode == ARETURN || opcode == ATHROW) {
622   *     dup();
623   *   } else {
624   *     if (opcode == LRETURN || opcode == DRETURN) {
625   *       dup2();
626   *     } else {
627   *       dup();
628   *     }
629   *     box(Type.getReturnType(this.methodDesc));
630   *   }
631   *   visitIntInsn(SIPUSH, opcode);
632   *   visitMethodInsn(INVOKESTATIC, owner, "onExit", "(Ljava/lang/Object;I)V");
633   * }
634   *
635   * // An actual call back method.
636   * public static void onExit(final Object exitValue, final int opcode) {
637   *   ...
638   * }
639   * </pre>
640   *
641   * @param opcode one of {@link Opcodes#RETURN}, {@link Opcodes#IRETURN}, {@link Opcodes#FRETURN},
642   *     {@link Opcodes#ARETURN}, {@link Opcodes#LRETURN}, {@link Opcodes#DRETURN} or {@link
643   *     Opcodes#ATHROW}.
644   */
645  protected void onMethodExit(final int opcode) {}
646}