647 lines
19 KiB
Java
647 lines
19 KiB
Java
/***
|
|
* ASM: a very small and fast Java bytecode manipulation framework
|
|
* Copyright (c) 2000-2011 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 java.util.List;
|
|
import java.util.Map;
|
|
|
|
import org.objectweb.asm.Handle;
|
|
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.MethodVisitor} 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 List<Object> stackFrame;
|
|
|
|
private Map<Label, List<Object>> branches;
|
|
|
|
/**
|
|
* Creates a new {@link AdviceAdapter}.
|
|
*
|
|
* @param api
|
|
* the ASM API version implemented by this visitor. Must be one
|
|
* of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
|
|
* @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}).
|
|
*/
|
|
protected AdviceAdapter(final int api, final MethodVisitor mv,
|
|
final int access, final String name, final String desc) {
|
|
super(api, mv, access, name, desc);
|
|
methodAccess = access;
|
|
methodDesc = desc;
|
|
constructor = "<init>".equals(name);
|
|
}
|
|
|
|
@Override
|
|
public void visitCode() {
|
|
mv.visitCode();
|
|
if (constructor) {
|
|
stackFrame = new ArrayList<Object>();
|
|
branches = new HashMap<Label, List<Object>>();
|
|
} else {
|
|
superInitialized = true;
|
|
onMethodEnter();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void visitLabel(final Label label) {
|
|
mv.visitLabel(label);
|
|
if (constructor && branches != null) {
|
|
List<Object> frame = branches.get(label);
|
|
if (frame != null) {
|
|
stackFrame = frame;
|
|
branches.remove(label);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void visitInsn(final int opcode) {
|
|
if (constructor) {
|
|
int s;
|
|
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();
|
|
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 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:
|
|
s = stackFrame.size();
|
|
stackFrame.add(s - 2, stackFrame.get(s - 1));
|
|
break;
|
|
case DUP_X2:
|
|
s = stackFrame.size();
|
|
stackFrame.add(s - 3, stackFrame.get(s - 1));
|
|
break;
|
|
case DUP2:
|
|
s = stackFrame.size();
|
|
stackFrame.add(s - 2, stackFrame.get(s - 1));
|
|
stackFrame.add(s - 2, stackFrame.get(s - 1));
|
|
break;
|
|
case DUP2_X1:
|
|
s = stackFrame.size();
|
|
stackFrame.add(s - 3, stackFrame.get(s - 1));
|
|
stackFrame.add(s - 3, stackFrame.get(s - 1));
|
|
break;
|
|
case DUP2_X2:
|
|
s = stackFrame.size();
|
|
stackFrame.add(s - 4, stackFrame.get(s - 1));
|
|
stackFrame.add(s - 4, stackFrame.get(s - 1));
|
|
break;
|
|
case SWAP:
|
|
s = stackFrame.size();
|
|
stackFrame.add(s - 2, stackFrame.get(s - 1));
|
|
stackFrame.remove(s);
|
|
break;
|
|
}
|
|
} else {
|
|
switch (opcode) {
|
|
case RETURN:
|
|
case IRETURN:
|
|
case FRETURN:
|
|
case ARETURN:
|
|
case LRETURN:
|
|
case DRETURN:
|
|
case ATHROW:
|
|
onMethodExit(opcode);
|
|
break;
|
|
}
|
|
}
|
|
mv.visitInsn(opcode);
|
|
}
|
|
|
|
@Override
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void visitIntInsn(final int opcode, final int operand) {
|
|
mv.visitIntInsn(opcode, operand);
|
|
if (constructor && opcode != NEWARRAY) {
|
|
pushValue(OTHER);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void visitLdcInsn(final Object cst) {
|
|
mv.visitLdcInsn(cst);
|
|
if (constructor) {
|
|
pushValue(OTHER);
|
|
if (cst instanceof Double || cst instanceof Long) {
|
|
pushValue(OTHER);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
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);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void visitTypeInsn(final int opcode, final String type) {
|
|
mv.visitTypeInsn(opcode, type);
|
|
// ANEWARRAY, CHECKCAST or INSTANCEOF don't change stack
|
|
if (constructor && opcode == NEW) {
|
|
pushValue(OTHER);
|
|
}
|
|
}
|
|
|
|
@Deprecated
|
|
@Override
|
|
public void visitMethodInsn(final int opcode, final String owner,
|
|
final String name, final String desc) {
|
|
if (api >= Opcodes.ASM5) {
|
|
super.visitMethodInsn(opcode, owner, name, desc);
|
|
return;
|
|
}
|
|
doVisitMethodInsn(opcode, owner, name, desc,
|
|
opcode == Opcodes.INVOKEINTERFACE);
|
|
}
|
|
|
|
@Override
|
|
public void visitMethodInsn(final int opcode, final String owner,
|
|
final String name, final String desc, final boolean itf) {
|
|
if (api < Opcodes.ASM5) {
|
|
super.visitMethodInsn(opcode, owner, name, desc, itf);
|
|
return;
|
|
}
|
|
doVisitMethodInsn(opcode, owner, name, desc, itf);
|
|
}
|
|
|
|
private void doVisitMethodInsn(int opcode, final String owner,
|
|
final String name, final String desc, final boolean itf) {
|
|
mv.visitMethodInsn(opcode, owner, name, desc, itf);
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm,
|
|
Object... bsmArgs) {
|
|
mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
|
|
if (constructor) {
|
|
Type[] types = Type.getArgumentTypes(desc);
|
|
for (int i = 0; i < types.length; i++) {
|
|
popValue();
|
|
if (types[i].getSize() == 2) {
|
|
popValue();
|
|
}
|
|
}
|
|
|
|
Type returnType = Type.getReturnType(desc);
|
|
if (returnType != Type.VOID_TYPE) {
|
|
pushValue(OTHER);
|
|
if (returnType.getSize() == 2) {
|
|
pushValue(OTHER);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
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);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void visitLookupSwitchInsn(final Label dflt, final int[] keys,
|
|
final Label[] labels) {
|
|
mv.visitLookupSwitchInsn(dflt, keys, labels);
|
|
if (constructor) {
|
|
popValue();
|
|
addBranches(dflt, labels);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
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);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void visitTryCatchBlock(Label start, Label end, Label handler,
|
|
String type) {
|
|
super.visitTryCatchBlock(start, end, handler, type);
|
|
if (constructor && !branches.containsKey(handler)) {
|
|
List<Object> stackFrame = new ArrayList<Object>();
|
|
stackFrame.add(OTHER);
|
|
branches.put(handler, stackFrame);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
branches.put(label, new ArrayList<Object>(stackFrame));
|
|
}
|
|
|
|
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 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 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(Object param, int opcode) {
|
|
* ...
|
|
* </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 void onMethodExit(int opcode) {
|
|
}
|
|
|
|
// TODO onException, onMethodCall
|
|
}
|