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.MethodVisitor; import org.objectweb.asm.Opcodes; import org.springframework.aop.framework.AdvisedSupport; /** * @author robh */ public class AdvisedCodeGenerationStrategy extends AbstractMethodProxyCodeGenerationStrategy { protected void generateMethod(MethodVisitor mv, Method method, AdvisedSupport advised, String proxyInternalName, String targetInternalName, String targetDescriptor) { boolean exposeProxy = advised.isExposeProxy(); boolean staticTargetSource = advised.getTargetSource().isStatic(); boolean requiresFinally = (exposeProxy || !(staticTargetSource)); String methodName = method.getName(); Class[] parameterTypes = method.getParameterTypes(); int localThis = 0; // used to calculate local variable indexes int parametersSize = calculateInitialLocalsOffset(parameterTypes); int localCounter = 1 + parametersSize; int localOldProxy = Integer.MIN_VALUE; if (exposeProxy) { localOldProxy = localCounter++; } int localAdvised = localCounter++; int localTargetSource = localCounter++; int localTargetClass = localCounter++; int localTarget = localCounter++; int localArgs = localCounter++; int localArgTypes = localCounter++; int localMethod = localCounter++; int localAdviceChain = localCounter++; int localMethodInvocation = localCounter++; int localReturnValue = localCounter++; // TODO: optimize local usage // TODO: need to rework try/catch/finally to match JDT compiler output // create a holder for the old proxy if needed if (exposeProxy) { mv.visitInsn(Opcodes.ACONST_NULL); mv.visitVarInsn(Opcodes.ASTORE, localOldProxy); } // load this mv.visitVarInsn(Opcodes.ALOAD, localThis); // load advised mv.visitFieldInsn(Opcodes.GETFIELD, proxyInternalName, ADVISED_FIELD_NAME, ADVISED_SUPPORT_DESCRIPTOR); mv.visitVarInsn(Opcodes.ASTORE, localAdvised); // load the target source mv.visitVarInsn(Opcodes.ALOAD, localAdvised); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, ADVISED_SUPPORT_INTERNAL_NAME, GET_TARGET_SOURCE_METHOD, GET_TARGET_SOURCE_DESCRIPTOR); mv.visitVarInsn(Opcodes.ASTORE, localTargetSource); // load the target class mv.visitVarInsn(Opcodes.ALOAD, localTargetSource); mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, TARGET_SOURCE_INTERNAL_NAME, GET_TARGET_CLASS_METHOD, GET_TARGET_CLASS_DESCRIPTOR); mv.visitVarInsn(Opcodes.ASTORE, localTargetClass); // try to get target early (otherwise set to null) if (staticTargetSource) { // load the target directly mv.visitVarInsn(Opcodes.ALOAD, localThis); mv.visitFieldInsn(Opcodes.GETFIELD, proxyInternalName, TARGET_FIELD_NAME, targetDescriptor); } else { mv.visitInsn(Opcodes.ACONST_NULL); } mv.visitVarInsn(Opcodes.ASTORE, localTarget); // start the try block Label openTry = new Label(); mv.visitLabel(openTry); // load the target from target source inside catch block if needed if (!staticTargetSource) { // load the target from the target source mv.visitVarInsn(Opcodes.ALOAD, localAdvised); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, ADVISED_SUPPORT_INTERNAL_NAME, GET_TARGET_SOURCE_METHOD, GET_TARGET_SOURCE_DESCRIPTOR); mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, TARGET_SOURCE_INTERNAL_NAME, GET_TARGET_METHOD, GET_TARGET_DESCRIPTOR); mv.visitTypeInsn(Opcodes.CHECKCAST, targetInternalName); mv.visitVarInsn(Opcodes.ASTORE, localTarget); } // bundle up the arguments into an object[] bundleArgs(mv, parameterTypes, localArgs); // create the arg types array bundleArgTypes(mv, parameterTypes, localArgTypes); // now get the method via reflection mv.visitVarInsn(Opcodes.ALOAD, localTargetClass); mv.visitLdcInsn(methodName); mv.visitVarInsn(Opcodes.ALOAD, localArgTypes); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, CLASS_INTERNAL_NAME, GET_METHOD_METHOD, GET_METHOD_DESCRIPTOR); mv.visitVarInsn(Opcodes.ASTORE, localMethod); // get the AdvisorChainFactory mv.visitVarInsn(Opcodes.ALOAD, localAdvised); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, ADVISED_SUPPORT_INTERNAL_NAME, GET_ADVISOR_CHAIN_FACTORY_METHOD, GET_ADVISOR_CHAIN_FACTORY_DESCRIPTOR); // get the advice chain mv.visitVarInsn(Opcodes.ALOAD, localAdvised); mv.visitInsn(Opcodes.ACONST_NULL); mv.visitVarInsn(Opcodes.ALOAD, localMethod); mv.visitVarInsn(Opcodes.ALOAD, localTargetClass); mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "org/springframework/aop/framework/AdvisorChainFactory", "getInterceptorsAndDynamicInterceptionAdvice", "(Lorg/springframework/aop/framework/Advised;Ljava/lang/Object;Ljava/lang/reflect/Method;Ljava/lang/Class;)Ljava/util/List;"); mv.visitVarInsn(Opcodes.ASTORE, localAdviceChain); // create the ReflectiveMethodInvocation object mv.visitTypeInsn(Opcodes.NEW, "org/springframework/aop/framework/ReflectiveMethodInvocation"); mv.visitInsn(Opcodes.DUP); // load the args mv.visitVarInsn(Opcodes.ALOAD, localThis); mv.visitVarInsn(Opcodes.ALOAD, localTarget); mv.visitVarInsn(Opcodes.ALOAD, localMethod); mv.visitVarInsn(Opcodes.ALOAD, localArgs); mv.visitVarInsn(Opcodes.ALOAD, localTargetClass); mv.visitVarInsn(Opcodes.ALOAD, localAdviceChain); // create invoke the constructor mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "org/springframework/aop/framework/ReflectiveMethodInvocation", "<init>", "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;Ljava/lang/Class;Ljava/util/List;)V"); mv.visitVarInsn(Opcodes.ASTORE, localMethodInvocation); // expose proxy if needed if (exposeProxy) { mv.visitVarInsn(Opcodes.ALOAD, localThis); mv.visitMethodInsn(Opcodes.INVOKESTATIC, AOP_CONTEXT_INTERNAL_NAME, SET_CURRENT_PROXY_METHOD, SET_CURRENT_PROXY_DESCRIPTOR); mv.visitVarInsn(Opcodes.ASTORE, localOldProxy); } // invoke proceed mv.visitVarInsn(Opcodes.ALOAD, localMethodInvocation); mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "org/aopalliance/intercept/MethodInvocation", "proceed", "()Ljava/lang/Object;"); // cast return value if required else pop Class returnType = method.getReturnType(); if (returnType == void.class) { mv.visitInsn(Opcodes.POP); } else { if (returnType.isPrimitive()) { visitUnwrapPrimtiveType(mv, returnType); } else { mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(returnType)); } if (requiresFinally) { mv.visitVarInsn(getStoreOpcodeForType(returnType), localReturnValue); } } // close the try block Label closeTry = new Label(); mv.visitLabel(closeTry); // create the first finally block if needed if (requiresFinally) { if (exposeProxy) { mv.visitVarInsn(Opcodes.ALOAD, localOldProxy); mv.visitMethodInsn(Opcodes.INVOKESTATIC, AOP_CONTEXT_INTERNAL_NAME, SET_CURRENT_PROXY_METHOD, SET_CURRENT_PROXY_DESCRIPTOR); mv.visitInsn(Opcodes.POP); } if (!staticTargetSource) { mv.visitVarInsn(Opcodes.ALOAD, localTargetSource); mv.visitVarInsn(Opcodes.ALOAD, localTarget); mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, TARGET_SOURCE_INTERNAL_NAME, "releaseTarget", "(Ljava/lang/Object;)V"); } if (returnType != void.class) { mv.visitVarInsn(getLoadOpcodeForType(returnType), localReturnValue); } } // exit block Label exit = new Label(); // return if required if (returnType != void.class) { mv.visitInsn(getReturnOpcodeForType(returnType)); } else { mv.visitJumpInsn(Opcodes.GOTO, exit); } // create the catch blocks for the exceptions on the method int stackThrowable = 1 + parametersSize; if (exposeProxy) { stackThrowable++; } // create the catch block for NoSuchMethodException Label catchNoSuchMethodException = new Label(); mv.visitLabel(catchNoSuchMethodException); mv.visitVarInsn(Opcodes.ASTORE, stackThrowable); mv.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalStateException"); mv.visitInsn(Opcodes.DUP); mv.visitLdcInsn("Unable to access joinpoint via reflection"); mv.visitVarInsn(Opcodes.ALOAD, stackThrowable); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/IllegalStateException", "<init>", "(Ljava/lang/String;Ljava/lang/Throwable;)V"); mv.visitInsn(Opcodes.ATHROW); // create the catch block for remaining Throwable Class[] exceptionTypes = method.getExceptionTypes(); Label[] handlerLabels = new Label[exceptionTypes.length]; for (int i = 0; i < exceptionTypes.length; i++) { Label handlerLabel = new Label(); mv.visitLabel(handlerLabel); mv.visitVarInsn(Opcodes.ASTORE, stackThrowable); mv.visitVarInsn(Opcodes.ALOAD, stackThrowable); mv.visitInsn(Opcodes.ATHROW); handlerLabels[i] = handlerLabel; } // catch runtime exceptions and throw them as they are Label catchRuntimeException = new Label(); mv.visitLabel(catchRuntimeException); mv.visitVarInsn(Opcodes.ASTORE, stackThrowable); mv.visitVarInsn(Opcodes.ALOAD, stackThrowable); mv.visitInsn(Opcodes.ATHROW); Label catchThrowable = new Label(); mv.visitLabel(catchThrowable); mv.visitVarInsn(Opcodes.ASTORE, stackThrowable); mv.visitTypeInsn(Opcodes.NEW, "java/lang/reflect/UndeclaredThrowableException"); mv.visitInsn(Opcodes.DUP); mv.visitVarInsn(Opcodes.ALOAD, stackThrowable); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/reflect/UndeclaredThrowableException", "<init>", "(Ljava/lang/Throwable;)V"); mv.visitInsn(Opcodes.ATHROW); Label catchForFinally = null; Label execptionalFinally = null; if (requiresFinally) { catchForFinally = new Label(); mv.visitLabel(catchForFinally); mv.visitVarInsn(Opcodes.ASTORE, stackThrowable); execptionalFinally = new Label(); mv.visitLabel(execptionalFinally); if (exposeProxy) { mv.visitVarInsn(Opcodes.ALOAD, localOldProxy); mv.visitMethodInsn(Opcodes.INVOKESTATIC, AOP_CONTEXT_INTERNAL_NAME, SET_CURRENT_PROXY_METHOD, SET_CURRENT_PROXY_DESCRIPTOR); mv.visitInsn(Opcodes.POP); } if (!staticTargetSource) { // TODO: need embedded try/catch block here mv.visitVarInsn(Opcodes.ALOAD, localTargetSource); mv.visitVarInsn(Opcodes.ALOAD, localTarget); mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, TARGET_SOURCE_INTERNAL_NAME, "releaseTarget", "(Ljava/lang/Object;)V"); } mv.visitVarInsn(Opcodes.ALOAD, stackThrowable); mv.visitInsn(Opcodes.ATHROW); } // close mv.visitLabel(exit); // return now for void if (returnType == void.class) { mv.visitInsn(getReturnOpcodeForType(method.getReturnType())); } for (int i = 0; i < handlerLabels.length; i++) { mv.visitTryCatchBlock(openTry, closeTry, handlerLabels[i], Type.getInternalName(exceptionTypes[i])); } mv.visitTryCatchBlock(openTry, closeTry, catchNoSuchMethodException, Type.getInternalName(NoSuchMethodException.class)); mv.visitTryCatchBlock(openTry, closeTry, catchRuntimeException, Type.getInternalName(RuntimeException.class)); mv.visitTryCatchBlock(openTry, closeTry, catchThrowable, Type.getInternalName(Throwable.class)); if (requiresFinally) { mv.visitTryCatchBlock(openTry, closeTry, catchForFinally, null); // TODO: test expose proxy with declared exceptions mv.visitTryCatchBlock(catchNoSuchMethodException, execptionalFinally, catchForFinally, null); } mv.visitMaxs(0, 0); } private void bundleArgTypes(MethodVisitor mv, Class[] parameterTypes, int stackIndex) { int size = parameterTypes.length; if (size == 0) { mv.visitInsn(Opcodes.ACONST_NULL); mv.visitVarInsn(Opcodes.ASTORE, stackIndex); } else { visitIntegerInsn(size, mv); mv.visitTypeInsn(Opcodes.ANEWARRAY, CLASS_INTERNAL_NAME); mv.visitVarInsn(Opcodes.ASTORE, stackIndex); for (int i = 0; i < parameterTypes.length; i++) { Class parameterType = parameterTypes[i]; // load the array mv.visitVarInsn(Opcodes.ALOAD, stackIndex); // load the index visitIntegerInsn(i, mv); // load the class if (parameterType.isPrimitive()) { visitGetPrimitiveType(mv, parameterType); } else { mv.visitLdcInsn(Type.getType(parameterType)); } // store in array mv.visitInsn(Opcodes.AASTORE); } } } private void bundleArgs(MethodVisitor mv, Class[] parameterTypes, int stackIndex) { // create the object array int size = parameterTypes.length; if (size == 0) { mv.visitInsn(Opcodes.ACONST_NULL); mv.visitVarInsn(Opcodes.ASTORE, stackIndex); } else { visitIntegerInsn(size, mv); mv.visitTypeInsn(Opcodes.ANEWARRAY, OBJECT_INTERNAL_NAME); mv.visitVarInsn(Opcodes.ASTORE, stackIndex); int stackCount = 1; for (int i = 0; i < parameterTypes.length; i++) { Class parameterType = parameterTypes[i]; // load the array mv.visitVarInsn(Opcodes.ALOAD, stackIndex); // load the index visitIntegerInsn(i, mv); // load the argument mv.visitVarInsn(getLoadOpcodeForType(parameterType), stackCount); if (parameterType.isPrimitive()) { // wrap primtive visitWrapPrimitive(mv, parameterType); } // store in array mv.visitInsn(Opcodes.AASTORE); stackCount += getLocalsSizeForType(parameterType); } } } }