package org.springframework.aop.framework.asm;
import java.lang.reflect.Method;
import org.objectweb.asm.Label;
import org.objectweb.asm.Type;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.MethodVisitor;
import org.springframework.aop.framework.AdvisedSupport;
/**
* @author robh
*/
public class NonStaticTargetSourceCodeGenerationStrategy extends AbstractMethodProxyCodeGenerationStrategy {
protected void generateMethod(MethodVisitor mv, Method method, AdvisedSupport advised, String proxyInternalName, String targetInternalName, String targetDescriptor) {
int initialLocalsOffset = calculateInitialLocalsOffset(method.getParameterTypes());
if (method.getReturnType() == void.class) {
doWithVoidReturn(mv, method, proxyInternalName, targetInternalName, initialLocalsOffset);
}
else {
doWithReturn(mv, method, advised, proxyInternalName, targetInternalName, targetDescriptor, initialLocalsOffset);
}
mv.visitMaxs(0, 0);
}
private void doWithVoidReturn(MethodVisitor cv, Method method, String proxyInternalName, String targetInternalName, int initialLocalsOffset) {
int localsCounter = initialLocalsOffset;
int localsTarget = ++localsCounter;
int localsTargetSource = ++localsCounter;
int localsCaughtException = ++localsCounter;
int localsJumpReturnAddress = ++localsCounter;
int localsUncaughtException = ++localsCounter;
visitInitTargetAndTargetSource(cv, proxyInternalName, localsTarget, localsTargetSource);
// define the needed labels
Label startTry = new Label();
Label finallyJumpPoint = new Label();
Label startCatch = new Label();
Label startSyntheticCatch = new Label();
Label startFinally = new Label();
Label startNestedTry = new Label();
Label finallyReturnPoint = new Label();
Label startNestedCatch = new Label();
Label exitPoint = new Label();
// start the try block
cv.visitLabel(startTry);
// load target from target source
visitLoadTargetFromTargetSource(cv, targetInternalName, localsTargetSource, localsTarget);
// invoke the target method
visitInvokeTargetMethod(cv, targetInternalName, method, localsTarget);
// goto normal finally block
cv.visitJumpInsn(Opcodes.GOTO, finallyJumpPoint);
// start the catch block
cv.visitLabel(startCatch);
// wrap and throw the exception
visitWrapAndThrow(cv, null, localsCaughtException);
// start the catch remaining block
cv.visitLabel(startSyntheticCatch);
visitExecuteFinallyAndThrow(cv, startFinally, localsUncaughtException);
// start the finally block
cv.visitLabel(startFinally);
cv.visitVarInsn(Opcodes.ASTORE, localsJumpReturnAddress);
cv.visitLabel(startNestedTry);
visitReleaseTarget(cv, localsTargetSource, localsTarget);
cv.visitJumpInsn(Opcodes.GOTO, finallyReturnPoint);
// start the nested catch block
cv.visitLabel(startNestedCatch);
visitWrapAndThrow(cv, "Unable to release target source", localsCaughtException);
// return from all JSRs
cv.visitLabel(finallyReturnPoint);
cv.visitVarInsn(Opcodes.RET, localsJumpReturnAddress);
// visit the standard finally jump marker
cv.visitLabel(finallyJumpPoint);
// jump to finally block
cv.visitJumpInsn(Opcodes.JSR, startFinally);
// mark the exit and return
cv.visitLabel(exitPoint);
cv.visitInsn(Opcodes.RETURN);
// mark the try/catch blocks
cv.visitTryCatchBlock(startTry, startCatch, startCatch, EXCEPTION_INTERNAL_NAME);
cv.visitTryCatchBlock(startTry, startSyntheticCatch, startSyntheticCatch, null);
cv.visitTryCatchBlock(finallyJumpPoint, exitPoint, startSyntheticCatch, null);
cv.visitTryCatchBlock(startNestedTry, startNestedCatch, startNestedCatch, EXCEPTION_INTERNAL_NAME);
}
private void doWithReturn(MethodVisitor cv, Method method, AdvisedSupport advised, String proxyInternalName, String targetInternalName, String targetDescriptor, int initialLocalsOffset) {
String descriptor = Type.getMethodDescriptor(method);
String methodName = method.getName();
int localsCounter = initialLocalsOffset;
int localsTarget = ++localsCounter;
int localsTargetSource = ++localsCounter;
int localsCaughtException = ++localsCounter;
int localsJumpReturnAddress = ++localsCounter;
int localsUncaughtException = ++localsCounter;
int localsReturnValue = ++localsCounter;
visitInitTargetAndTargetSource(cv, proxyInternalName, localsTarget, localsTargetSource);
// define needed labels
Label startTry = new Label();
Label startFinally = new Label();
Label endTry = new Label();
Label startCatch = new Label();
Label startSyntheticCatch = new Label();
Label startNestedTry = new Label();
Label exitPoint = new Label();
Label startNestedCatch = new Label();
// start the try block
cv.visitLabel(startTry);
// load target from target source
visitLoadTargetFromTargetSource(cv, targetInternalName, localsTargetSource, localsTarget);
// invoke the target method
visitInvokeTargetMethod(cv, targetInternalName, method, localsTarget);
// store the return value
cv.visitVarInsn(Opcodes.ASTORE, localsReturnValue);
// jump to finally block
cv.visitJumpInsn(Opcodes.JSR, startFinally);
// mark the end of the try block (including the call to finally)
cv.visitLabel(endTry);
// load the return value and return
Class returnType = method.getReturnType();
cv.visitVarInsn(getLoadOpcodeForType(returnType), localsReturnValue);
cv.visitInsn(getReturnOpcodeForType(returnType));
// mark the start of the defined catch block
cv.visitLabel(startCatch);
visitWrapAndThrow(cv, null, localsCaughtException);
// mark the start of the synthetic catch block
cv.visitLabel(startSyntheticCatch);
visitExecuteFinallyAndThrow(cv, startFinally, localsUncaughtException);
// visit the finally block
cv.visitLabel(startFinally);
cv.visitVarInsn(Opcodes.ASTORE, localsJumpReturnAddress);
// start the nested try block
cv.visitLabel(startNestedTry);
visitReleaseTarget(cv, localsTargetSource, localsTarget);
cv.visitJumpInsn(Opcodes.GOTO, exitPoint);
// start the nested catch block
cv.visitLabel(startNestedCatch);
visitWrapAndThrow(cv, "Unable to release target source", localsCaughtException);
// mark the exit point
cv.visitLabel(exitPoint);
cv.visitVarInsn(Opcodes.RET, localsJumpReturnAddress);
// visit try/catch blocks
cv.visitTryCatchBlock(startTry, startCatch, startCatch, EXCEPTION_INTERNAL_NAME);
cv.visitTryCatchBlock(startTry, endTry, startSyntheticCatch, null);
cv.visitTryCatchBlock(startCatch, startSyntheticCatch, startSyntheticCatch, null);
cv.visitTryCatchBlock(startNestedTry, startNestedCatch, startNestedCatch, EXCEPTION_INTERNAL_NAME);
}
private void visitReleaseTarget(MethodVisitor cv, int localsTargetSource, int localsTarget) {
cv.visitVarInsn(Opcodes.ALOAD, localsTargetSource);
cv.visitVarInsn(Opcodes.ALOAD, localsTarget);
cv.visitMethodInsn(Opcodes.INVOKEINTERFACE, TARGET_SOURCE_INTERNAL_NAME, RELEASE_TARGET_METHOD, RELEASE_TARGET_DESCRIPTOR);
}
private void visitExecuteFinallyAndThrow(MethodVisitor cv, Label finallyBlock, int localsException) {
cv.visitVarInsn(Opcodes.ASTORE, localsException);
cv.visitJumpInsn(Opcodes.JSR, finallyBlock);
cv.visitVarInsn(Opcodes.ALOAD, localsException);
cv.visitInsn(Opcodes.ATHROW);
}
private void visitWrapAndThrow(MethodVisitor cv, String message, int localsWrappedException) {
cv.visitVarInsn(Opcodes.ASTORE, localsWrappedException);
cv.visitTypeInsn(Opcodes.NEW, UNDECLARED_THROWABLE_EXCEPTION_INTERNAL_NAME);
cv.visitInsn(Opcodes.DUP);
String exceptionConstructorDescriptor = SINGLE_ARG_EXCEPTION_CONSTRUCTOR_DESCRIPTOR;
if (message != null) {
cv.visitLdcInsn(message);
exceptionConstructorDescriptor = EXCEPTION_CONSTRUCTOR_DESCRIPTOR;
}
cv.visitVarInsn(Opcodes.ALOAD, localsWrappedException);
cv.visitMethodInsn(Opcodes.INVOKESPECIAL, UNDECLARED_THROWABLE_EXCEPTION_INTERNAL_NAME, CONSTRUCTOR_INTERNAL_NAME, exceptionConstructorDescriptor);
cv.visitInsn(Opcodes.ATHROW);
}
private void visitInvokeTargetMethod(MethodVisitor cv, String targetInternalName, Method method, int localsTarget) {
// get the target back
cv.visitVarInsn(Opcodes.ALOAD, localsTarget);
// load args
Class[] parameterTypes = method.getParameterTypes();
int stackCount = 1;
int argCount = parameterTypes.length;
if (argCount > 0) {
for (int x = 0; x < argCount; x++) {
Class parameterType = parameterTypes[x];
int frameSpaceSize = getLocalsSizeForType(parameterType);
cv.visitVarInsn(getLoadOpcodeForType(parameterTypes[x]), stackCount);
stackCount += frameSpaceSize;
}
}
// invoke
String methodName = method.getName();
String methodDescriptor = Type.getMethodDescriptor(method);
cv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, targetInternalName, methodName, methodDescriptor);
}
private void visitLoadTargetFromTargetSource(MethodVisitor cv, String targetInternalName, int localsTargetSource, int localsTarget) {
// get the target from the target source
cv.visitVarInsn(Opcodes.ALOAD, localsTargetSource);
cv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, TARGET_SOURCE_INTERNAL_NAME, GET_TARGET_METHOD, GET_TARGET_DESCRIPTOR);
// cast to appropriate type
cv.visitTypeInsn(Opcodes.CHECKCAST, targetInternalName); // TODO: try removing this cast for optimization
// save for later
cv.visitVarInsn(Opcodes.ASTORE, localsTarget);
}
private void visitInitTargetAndTargetSource(MethodVisitor cv, String proxyInternalName, int localsTarget, int localsTargetSource) {
cv.visitInsn(Opcodes.ACONST_NULL);
cv.visitVarInsn(Opcodes.ASTORE, localsTarget);
cv.visitVarInsn(Opcodes.ALOAD, 0); // load this
cv.visitFieldInsn(Opcodes.GETFIELD, proxyInternalName, ADVISED_FIELD_NAME, ADVISED_SUPPORT_DESCRIPTOR);
cv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, ADVISED_SUPPORT_INTERNAL_NAME, GET_TARGET_SOURCE_METHOD, GET_TARGET_SOURCE_DESCRIPTOR);
cv.visitVarInsn(Opcodes.ASTORE, localsTargetSource);
}
}