/*** | |
* ASM: a very small and fast Java bytecode manipulation framework | |
* Copyright (c) 2000-2005 INRIA, France Telecom | |
* All rights reserved. | |
* | |
* Redistribution and use in source and binary forms, with or without | |
* modification, are permitted provided that the following conditions | |
* are met: | |
* 1. Redistributions of source code must retain the above copyright | |
* notice, this list of conditions and the following disclaimer. | |
* 2. Redistributions in binary form must reproduce the above copyright | |
* notice, this list of conditions and the following disclaimer in the | |
* documentation and/or other materials provided with the distribution. | |
* 3. Neither the name of the copyright holders nor the names of its | |
* contributors may be used to endorse or promote products derived from | |
* this software without specific prior written permission. | |
* | |
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF | |
* THE POSSIBILITY OF SUCH DAMAGE. | |
*/ | |
package org.objectweb.asm.commons; | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import org.objectweb.asm.Label; | |
import org.objectweb.asm.MethodVisitor; | |
import org.objectweb.asm.Opcodes; | |
import org.objectweb.asm.Type; | |
/** | |
* A {@link org.objectweb.asm.MethodAdapter} to insert before, after and around | |
* advices in methods and constructors. <p> The behavior for constructors is | |
* like this: <ol> | |
* | |
* <li>as long as the INVOKESPECIAL for the object initialization has not been | |
* reached, every bytecode instruction is dispatched in the ctor code visitor</li> | |
* | |
* <li>when this one is reached, it is only added in the ctor code visitor and | |
* a JP invoke is added</li> | |
* | |
* <li>after that, only the other code visitor receives the instructions</li> | |
* | |
* </ol> | |
* | |
* @author Eugene Kuleshov | |
* @author Eric Bruneton | |
*/ | |
public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes | |
{ | |
private static final Object THIS = new Object(); | |
private static final Object OTHER = new Object(); | |
protected int methodAccess; | |
protected String methodDesc; | |
private boolean constructor; | |
private boolean superInitialized; | |
private ArrayList stackFrame; | |
private HashMap branches; | |
/** | |
* Creates a new {@link AdviceAdapter}. | |
* | |
* @param mv the method visitor to which this adapter delegates calls. | |
* @param access the method's access flags (see {@link Opcodes}). | |
* @param name the method's name. | |
* @param desc the method's descriptor (see {@link Type Type}). | |
*/ | |
public AdviceAdapter( | |
final MethodVisitor mv, | |
final int access, | |
final String name, | |
final String desc) | |
{ | |
super(mv, access, name, desc); | |
methodAccess = access; | |
methodDesc = desc; | |
constructor = "<init>".equals(name); | |
} | |
public void visitCode() { | |
mv.visitCode(); | |
if (!constructor) { | |
superInitialized = true; | |
onMethodEnter(); | |
} else { | |
stackFrame = new ArrayList(); | |
branches = new HashMap(); | |
} | |
} | |
public void visitLabel(final Label label) { | |
mv.visitLabel(label); | |
if (constructor && branches != null) { | |
ArrayList frame = (ArrayList) branches.get(label); | |
if (frame != null) { | |
stackFrame = frame; | |
branches.remove(label); | |
} | |
} | |
} | |
public void visitInsn(final int opcode) { | |
if (constructor) { | |
switch (opcode) { | |
case RETURN: // empty stack | |
onMethodExit(opcode); | |
break; | |
case IRETURN: // 1 before n/a after | |
case FRETURN: // 1 before n/a after | |
case ARETURN: // 1 before n/a after | |
case ATHROW: // 1 before n/a after | |
popValue(); | |
popValue(); | |
onMethodExit(opcode); | |
break; | |
case LRETURN: // 2 before n/a after | |
case DRETURN: // 2 before n/a after | |
popValue(); | |
popValue(); | |
onMethodExit(opcode); | |
break; | |
case NOP: | |
case LALOAD: // remove 2 add 2 | |
case DALOAD: // remove 2 add 2 | |
case LNEG: | |
case DNEG: | |
case FNEG: | |
case INEG: | |
case L2D: | |
case D2L: | |
case F2I: | |
case I2B: | |
case I2C: | |
case I2S: | |
case I2F: | |
case Opcodes.ARRAYLENGTH: | |
break; | |
case ACONST_NULL: | |
case ICONST_M1: | |
case ICONST_0: | |
case ICONST_1: | |
case ICONST_2: | |
case ICONST_3: | |
case ICONST_4: | |
case ICONST_5: | |
case FCONST_0: | |
case FCONST_1: | |
case FCONST_2: | |
case F2L: // 1 before 2 after | |
case F2D: | |
case I2L: | |
case I2D: | |
pushValue(OTHER); | |
break; | |
case LCONST_0: | |
case LCONST_1: | |
case DCONST_0: | |
case DCONST_1: | |
pushValue(OTHER); | |
pushValue(OTHER); | |
break; | |
case IALOAD: // remove 2 add 1 | |
case FALOAD: // remove 2 add 1 | |
case AALOAD: // remove 2 add 1 | |
case BALOAD: // remove 2 add 1 | |
case CALOAD: // remove 2 add 1 | |
case SALOAD: // remove 2 add 1 | |
case POP: | |
case IADD: | |
case FADD: | |
case ISUB: | |
case LSHL: // 3 before 2 after | |
case LSHR: // 3 before 2 after | |
case LUSHR: // 3 before 2 after | |
case L2I: // 2 before 1 after | |
case L2F: // 2 before 1 after | |
case D2I: // 2 before 1 after | |
case D2F: // 2 before 1 after | |
case FSUB: | |
case FMUL: | |
case FDIV: | |
case FREM: | |
case FCMPL: // 2 before 1 after | |
case FCMPG: // 2 before 1 after | |
case IMUL: | |
case IDIV: | |
case IREM: | |
case ISHL: | |
case ISHR: | |
case IUSHR: | |
case IAND: | |
case IOR: | |
case IXOR: | |
case MONITORENTER: | |
case MONITOREXIT: | |
popValue(); | |
break; | |
case POP2: | |
case LSUB: | |
case LMUL: | |
case LDIV: | |
case LREM: | |
case LADD: | |
case LAND: | |
case LOR: | |
case LXOR: | |
case DADD: | |
case DMUL: | |
case DSUB: | |
case DDIV: | |
case DREM: | |
popValue(); | |
popValue(); | |
break; | |
case IASTORE: | |
case FASTORE: | |
case AASTORE: | |
case BASTORE: | |
case CASTORE: | |
case SASTORE: | |
case LCMP: // 4 before 1 after | |
case DCMPL: | |
case DCMPG: | |
popValue(); | |
popValue(); | |
popValue(); | |
break; | |
case LASTORE: | |
case DASTORE: | |
popValue(); | |
popValue(); | |
popValue(); | |
popValue(); | |
break; | |
case DUP: | |
pushValue(peekValue()); | |
break; | |
case DUP_X1: | |
// TODO optimize this | |
{ | |
Object o1 = popValue(); | |
Object o2 = popValue(); | |
pushValue(o1); | |
pushValue(o2); | |
pushValue(o1); | |
} | |
break; | |
case DUP_X2: | |
// TODO optimize this | |
{ | |
Object o1 = popValue(); | |
Object o2 = popValue(); | |
Object o3 = popValue(); | |
pushValue(o1); | |
pushValue(o3); | |
pushValue(o2); | |
pushValue(o1); | |
} | |
break; | |
case DUP2: | |
// TODO optimize this | |
{ | |
Object o1 = popValue(); | |
Object o2 = popValue(); | |
pushValue(o2); | |
pushValue(o1); | |
pushValue(o2); | |
pushValue(o1); | |
} | |
break; | |
case DUP2_X1: | |
// TODO optimize this | |
{ | |
Object o1 = popValue(); | |
Object o2 = popValue(); | |
Object o3 = popValue(); | |
pushValue(o2); | |
pushValue(o1); | |
pushValue(o3); | |
pushValue(o2); | |
pushValue(o1); | |
} | |
break; | |
case DUP2_X2: | |
// TODO optimize this | |
{ | |
Object o1 = popValue(); | |
Object o2 = popValue(); | |
Object o3 = popValue(); | |
Object o4 = popValue(); | |
pushValue(o2); | |
pushValue(o1); | |
pushValue(o4); | |
pushValue(o3); | |
pushValue(o2); | |
pushValue(o1); | |
} | |
break; | |
case SWAP: { | |
Object o1 = popValue(); | |
Object o2 = popValue(); | |
pushValue(o1); | |
pushValue(o2); | |
} | |
break; | |
} | |
} else { | |
switch (opcode) { | |
case RETURN: | |
case IRETURN: | |
case FRETURN: | |
case ARETURN: | |
case LRETURN: | |
case DRETURN: | |
case ATHROW: | |
onMethodExit(opcode); | |
break; | |
} | |
} | |
mv.visitInsn(opcode); | |
} | |
public void visitVarInsn(final int opcode, final int var) { | |
super.visitVarInsn(opcode, var); | |
if (constructor) { | |
switch (opcode) { | |
case ILOAD: | |
case FLOAD: | |
pushValue(OTHER); | |
break; | |
case LLOAD: | |
case DLOAD: | |
pushValue(OTHER); | |
pushValue(OTHER); | |
break; | |
case ALOAD: | |
pushValue(var == 0 ? THIS : OTHER); | |
break; | |
case ASTORE: | |
case ISTORE: | |
case FSTORE: | |
popValue(); | |
break; | |
case LSTORE: | |
case DSTORE: | |
popValue(); | |
popValue(); | |
break; | |
} | |
} | |
} | |
public void visitFieldInsn( | |
final int opcode, | |
final String owner, | |
final String name, | |
final String desc) | |
{ | |
mv.visitFieldInsn(opcode, owner, name, desc); | |
if (constructor) { | |
char c = desc.charAt(0); | |
boolean longOrDouble = c == 'J' || c == 'D'; | |
switch (opcode) { | |
case GETSTATIC: | |
pushValue(OTHER); | |
if (longOrDouble) { | |
pushValue(OTHER); | |
} | |
break; | |
case PUTSTATIC: | |
popValue(); | |
if (longOrDouble) { | |
popValue(); | |
} | |
break; | |
case PUTFIELD: | |
popValue(); | |
if (longOrDouble) { | |
popValue(); | |
popValue(); | |
} | |
break; | |
// case GETFIELD: | |
default: | |
if (longOrDouble) { | |
pushValue(OTHER); | |
} | |
} | |
} | |
} | |
public void visitIntInsn(final int opcode, final int operand) { | |
mv.visitIntInsn(opcode, operand); | |
if (constructor && opcode!=NEWARRAY) { | |
pushValue(OTHER); | |
} | |
} | |
public void visitLdcInsn(final Object cst) { | |
mv.visitLdcInsn(cst); | |
if (constructor) { | |
pushValue(OTHER); | |
if (cst instanceof Double || cst instanceof Long) { | |
pushValue(OTHER); | |
} | |
} | |
} | |
public void visitMultiANewArrayInsn(final String desc, final int dims) { | |
mv.visitMultiANewArrayInsn(desc, dims); | |
if (constructor) { | |
for (int i = 0; i < dims; i++) { | |
popValue(); | |
} | |
pushValue(OTHER); | |
} | |
} | |
public void visitTypeInsn(final int opcode, final String name) { | |
mv.visitTypeInsn(opcode, name); | |
// ANEWARRAY, CHECKCAST or INSTANCEOF don't change stack | |
if (constructor && opcode == NEW) { | |
pushValue(OTHER); | |
} | |
} | |
public void visitMethodInsn( | |
final int opcode, | |
final String owner, | |
final String name, | |
final String desc) | |
{ | |
mv.visitMethodInsn(opcode, owner, name, desc); | |
if (constructor) { | |
Type[] types = Type.getArgumentTypes(desc); | |
for (int i = 0; i < types.length; i++) { | |
popValue(); | |
if (types[i].getSize() == 2) { | |
popValue(); | |
} | |
} | |
switch (opcode) { | |
// case INVOKESTATIC: | |
// break; | |
case INVOKEINTERFACE: | |
case INVOKEVIRTUAL: | |
popValue(); // objectref | |
break; | |
case INVOKESPECIAL: | |
Object type = popValue(); // objectref | |
if (type == THIS && !superInitialized) { | |
onMethodEnter(); | |
superInitialized = true; | |
// once super has been initialized it is no longer | |
// necessary to keep track of stack state | |
constructor = false; | |
} | |
break; | |
} | |
Type returnType = Type.getReturnType(desc); | |
if (returnType != Type.VOID_TYPE) { | |
pushValue(OTHER); | |
if (returnType.getSize() == 2) { | |
pushValue(OTHER); | |
} | |
} | |
} | |
} | |
public void visitJumpInsn(final int opcode, final Label label) { | |
mv.visitJumpInsn(opcode, label); | |
if (constructor) { | |
switch (opcode) { | |
case IFEQ: | |
case IFNE: | |
case IFLT: | |
case IFGE: | |
case IFGT: | |
case IFLE: | |
case IFNULL: | |
case IFNONNULL: | |
popValue(); | |
break; | |
case IF_ICMPEQ: | |
case IF_ICMPNE: | |
case IF_ICMPLT: | |
case IF_ICMPGE: | |
case IF_ICMPGT: | |
case IF_ICMPLE: | |
case IF_ACMPEQ: | |
case IF_ACMPNE: | |
popValue(); | |
popValue(); | |
break; | |
case JSR: | |
pushValue(OTHER); | |
break; | |
} | |
addBranch(label); | |
} | |
} | |
public void visitLookupSwitchInsn( | |
final Label dflt, | |
final int[] keys, | |
final Label[] labels) | |
{ | |
mv.visitLookupSwitchInsn(dflt, keys, labels); | |
if (constructor) { | |
popValue(); | |
addBranches(dflt, labels); | |
} | |
} | |
public void visitTableSwitchInsn( | |
final int min, | |
final int max, | |
final Label dflt, | |
final Label[] labels) | |
{ | |
mv.visitTableSwitchInsn(min, max, dflt, labels); | |
if (constructor) { | |
popValue(); | |
addBranches(dflt, labels); | |
} | |
} | |
private void addBranches(final Label dflt, final Label[] labels) { | |
addBranch(dflt); | |
for (int i = 0; i < labels.length; i++) { | |
addBranch(labels[i]); | |
} | |
} | |
private void addBranch(final Label label) { | |
if (branches.containsKey(label)) { | |
return; | |
} | |
ArrayList frame = new ArrayList(); | |
frame.addAll(stackFrame); | |
branches.put(label, frame); | |
} | |
private Object popValue() { | |
return stackFrame.remove(stackFrame.size() - 1); | |
} | |
private Object peekValue() { | |
return stackFrame.get(stackFrame.size() - 1); | |
} | |
private void pushValue(final Object o) { | |
stackFrame.add(o); | |
} | |
/** | |
* Called at the beginning of the method or after super class class call in | |
* the constructor. <br><br> | |
* | |
* <i>Custom code can use or change all the local variables, but should not | |
* change state of the stack.</i> | |
*/ | |
protected abstract void onMethodEnter(); | |
/** | |
* Called before explicit exit from the method using either return or throw. | |
* Top element on the stack contains the return value or exception instance. | |
* For example: | |
* | |
* <pre> | |
* public void onMethodExit(int opcode) { | |
* if(opcode==RETURN) { | |
* visitInsn(ACONST_NULL); | |
* } else if(opcode==ARETURN || opcode==ATHROW) { | |
* dup(); | |
* } else { | |
* if(opcode==LRETURN || opcode==DRETURN) { | |
* dup2(); | |
* } else { | |
* dup(); | |
* } | |
* box(Type.getReturnType(this.methodDesc)); | |
* } | |
* visitIntInsn(SIPUSH, opcode); | |
* visitMethodInsn(INVOKESTATIC, owner, "onExit", "(Ljava/lang/Object;I)V"); | |
* } | |
* | |
* // an actual call back method | |
* public static void onExit(int opcode, Object param) { | |
* ... | |
* </pre> | |
* | |
* <br><br> | |
* | |
* <i>Custom code can use or change all the local variables, but should not | |
* change state of the stack.</i> | |
* | |
* @param opcode one of the RETURN, IRETURN, FRETURN, ARETURN, LRETURN, | |
* DRETURN or ATHROW | |
* | |
*/ | |
protected abstract void onMethodExit(int opcode); | |
// TODO onException, onMethodCall | |
} |