
org.test4j.mock.faking.modifier.FakeMethodModifier Maven / Gradle / Ivy
package org.test4j.mock.faking.modifier;
import g_asm.org.objectweb.asm.Label;
import g_asm.org.objectweb.asm.MethodVisitor;
import g_asm.org.objectweb.asm.Opcodes;
import g_asm.org.objectweb.asm.Type;
import org.test4j.mock.faking.FakeInvoker;
import org.test4j.mock.faking.util.TypeUtility;
import java.lang.reflect.Method;
import static g_asm.org.objectweb.asm.Opcodes.*;
import static g_asm.org.objectweb.asm.Type.VOID;
import static java.lang.reflect.Modifier.isStatic;
import static org.test4j.mock.faking.util.AsmConstant.api_code;
import static org.test4j.mock.faking.util.AsmType.*;
import static org.test4j.mock.faking.util.TypeDesc.*;
import static org.test4j.mock.faking.util.TypeUtility.isConstructor;
/**
* 动态修改被mock方法实现
*
* @author darui.wu
*/
public class FakeMethodModifier extends MethodVisitor {
private final FakeClassModifier cv;
private final int access;
private final String name;
private final String desc;
private final boolean isConstructor;
FakeMethodModifier(FakeClassModifier cv, MethodVisitor mv, int access, String name, String desc) {
super(api_code, mv);
this.cv = cv;
this.access = access;
this.name = name;
this.desc = desc;
this.isConstructor = TypeUtility.isConstructor(name);
if (!this.isConstructor) {
this.callFakeMethod();
}
}
private boolean alreadyCopied;
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
mv.visitMethodInsn(opcode, owner, name, desc, itf);
if (this.fakeConstructor(owner, opcode, name)) {
this.callFakeConstructor();
alreadyCopied = true;
}
}
/**
* 紧跟在 this() or super()指令后面
*
* @param owner
* @param opcode
* @param name
* @return
*/
private boolean fakeConstructor(String owner, int opcode, String name) {
if (!this.isConstructor || alreadyCopied) {
return false;
}
if (!isConstructor(name) || opcode != INVOKESPECIAL) {
return false;
} else {
// this(...) 或者 super(...)方法
return owner.equals(this.cv.superClassName) || owner.equals(this.cv.classDesc);
}
}
/**
* For some reason, the start position for "this" gets displaced by byte code inserted at the beginning,
* in a method modified by the EMMA tool. If not treated, this causes a ClassFormatError.
*/
@Override
public final void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
if (end.getOffset() > 0) {
Label _start = start.getOffset() > end.getOffset() ? end : start;
mv.visitLocalVariable(name, desc, signature, _start, end, index);
}
}
/**
* 使用局部变量方式
*/
public void callFakeMethod() {
Type[] argTypes = Type.getArgumentTypes(this.desc);
int index = this.argsObjectArray(argTypes);
super.visitVarInsn(ASTORE, index + 1);
this.byteCallFakeMethod(index + 1);
super.visitVarInsn(ASTORE, index + 2);
super.visitVarInsn(ALOAD, index + 2);
//super.visitMethodInsn(Opcodes.INVOKESTATIC, T_String.PATH, "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;", false);
this.visitLdcInsn(BridgeFakeInvocation.Enter_Non_Mock_Block);
Label label = new Label();
this.visitJumpInsn(IF_ACMPEQ, label);
this.byteReturnValue(index + 2);
this.visitLabel(label);
// super.visitMaxs(0, 0);
}
/**
* 不使用局部变量, 要不然和jacoco一起使用会报VerifyError
*/
public void callFakeConstructor() {
super.visitFieldInsn(GETSTATIC, FakeInvoker.getHostClassName(), BridgeFakeInvocation.BridgeID, T_InvocationHandler.DESC);
/** 1st "invoke" arguments **/
this.push1stArgThis();
/** 2nd "invoke" arguments **/
this.push2ndArgMethod();
/** 3rd new Object[]{...}**/
Type[] argTypes = Type.getArgumentTypes(this.desc);
this.argsObjectArray(argTypes);
//
super.visitMethodInsn(INVOKEINTERFACE, T_InvocationHandler.PATH, "invoke", Invocation_Desc, true);
this.visitLdcInsn(BridgeFakeInvocation.Enter_Non_Mock_Block);
Label label = new Label();
this.visitJumpInsn(IF_ACMPEQ, label);
super.visitInsn(RETURN);
this.visitLabel(label);
// super.visitMaxs(0, 0);
}
private int argsObjectArray(Type[] argTypes) {
/** 定义Object[]数组大小 **/
super.visitIntInsn(BIPUSH, 3 + argTypes.length);
super.visitTypeInsn(ANEWARRAY, T_Object.PATH);
// Object[0]
this.visitStringOfArray(0, cv.classDesc);
// Object[1]
this.visitStringOfArray(1, name);
// Object[2]
this.visitStringOfArray(2, desc);
/** 将 args[] 追加到 Object[] 数组中 **/
int localVarIndex = isStatic(this.access) ? 0 : 1;
for (int index = 0; index < argTypes.length; index++) {
Type type = argTypes[index];
this.visitTypeOfArray(3 + index, type, localVarIndex);
localVarIndex += argTypes[index].getSize();
}
return localVarIndex - (isStatic(this.access) ? 0 : 1);
}
private void byteCallFakeMethod(int localIndex) {
super.visitFieldInsn(GETSTATIC, FakeInvoker.getHostClassName(), BridgeFakeInvocation.BridgeID, T_InvocationHandler.DESC);
/** 1st "invoke" arguments **/
this.push1stArgThis();
/** 2nd "invoke" arguments **/
this.push2ndArgMethod();
/** 3rd arg: new Object[]{...} **/
super.visitVarInsn(ALOAD, localIndex);
super.visitMethodInsn(INVOKEINTERFACE, T_InvocationHandler.PATH, "invoke", Invocation_Desc, true);
}
private final void byteReturnValue(int localIndex) {
Type returnType = Type.getReturnType(this.desc);
if (returnType.getSort() == VOID) {
super.visitInsn(returnType.getOpcode(IRETURN));
return;
}
super.visitVarInsn(ALOAD, localIndex);
this.visitTypeInsn(Opcodes.CHECKCAST, getTypeDesc(returnType));
if (isPrimitive(returnType)) {
String methodName = returnType.getClassName() + "Value";
String typePath = PRIMITIVE_OBJECTS.get(returnType.getSort()).PATH;
String methodDesc = "()" + returnType.getInternalName();
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, typePath, methodName, methodDesc, false);
}
super.visitInsn(returnType.getOpcode(IRETURN));
}
/**
* 参数入栈: Object[index] = args[localVarIndex]
*
* @param index
* @param type
* @param localVarIndex
*/
private void visitTypeOfArray(int index, Type type, int localVarIndex) {
super.visitInsn(DUP);
super.visitIntInsn(SIPUSH, index);
super.visitVarInsn(type.getOpcode(ILOAD), localVarIndex);
if (isPrimitive(type)) {
String typePath = PRIMITIVE_OBJECTS.get(type.getSort()).PATH;
String typeDesc = '(' + type.getDescriptor() + ")L" + typePath + ';';
super.visitMethodInsn(Opcodes.INVOKESTATIC, typePath, "valueOf", typeDesc, false);
}
super.visitInsn(AASTORE);
}
/**
* 压入 value 到 Object[index] 位置
*
* @param index
* @param value
*/
private final void visitStringOfArray(int index, String value) {
super.visitInsn(DUP);
super.visitIntInsn(SIPUSH, index);
super.visitLdcInsn(value);
super.visitInsn(AASTORE);
}
/**
* 压入第二个变量 (Method)null
*/
private void push2ndArgMethod() {
super.visitInsn(ACONST_NULL);
super.visitTypeInsn(CHECKCAST, T_Method.PATH);
}
/**
* 压入第一个变量this或null(静态方法)
*/
private void push1stArgThis() {
if (isStatic(this.access)) {
super.visitInsn(ACONST_NULL);
} else {
super.visitVarInsn(ALOAD, 0);
}
}
/**
* {@link java.lang.reflect.InvocationHandler#invoke(Object, Method, Object[])}
*/
final static String Invocation_Desc = String.format("(%s%s[%s)%s", T_Object.DESC, T_Method.DESC, T_Object.DESC, T_Object.DESC);
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy