/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.aries.proxy.impl.common; import static java.lang.String.format; import static org.apache.aries.proxy.impl.common.AbstractWovenProxyAdapter.DISPATCHER_FIELD; import static org.apache.aries.proxy.impl.common.AbstractWovenProxyAdapter.DISPATCHER_TYPE; import static org.apache.aries.proxy.impl.common.AbstractWovenProxyAdapter.LISTENER_FIELD; import static org.apache.aries.proxy.impl.common.AbstractWovenProxyAdapter.LISTENER_TYPE; import static org.apache.aries.proxy.impl.common.AbstractWovenProxyAdapter.METHOD_TYPE; import static org.apache.aries.proxy.impl.common.AbstractWovenProxyAdapter.NO_ARGS; import static org.apache.aries.proxy.impl.common.AbstractWovenProxyAdapter.OBJECT_TYPE; import static org.apache.aries.proxy.impl.common.AbstractWovenProxyAdapter.THROWABLE_INAME; import static org.apache.aries.proxy.impl.common.AbstractWovenProxyAdapter.WOVEN_PROXY_IFACE_TYPE; import static org.objectweb.asm.Opcodes.ACONST_NULL; import static org.objectweb.asm.Opcodes.IFNE; import static org.objectweb.asm.Opcodes.ASM5; import java.util.Arrays; import org.apache.aries.proxy.InvocationListener; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.GeneratorAdapter; import org.objectweb.asm.commons.Method; /** * This class weaves dispatch and listener code into a method, there are two known * subclasses {@link WovenProxyConcreteMethodAdapter} is used for weaving instance methods * {@link WovenProxyAbstractMethodAdapter} is used to provide a delegating * implementation of an interface method. * * Roughly (but not exactly because it's easier to write working bytecode * if you don't have to exactly recreate the Java!) this is trying to * do the following: <code> * * if(dispatcher != null) { int returnValue; Object token = null; boolean inInvoke = false; try { Object toInvoke = dispatcher.call(); if(listener != null) token = listener.preInvoke(toInvoke, method, args); inInvoke = true; returnValue = ((Template) toInvoke).doStuff(args); inInvoke = false; if(listener != null) listener.postInvoke(token, toInvoke, method, args); } catch (Throwable e){ // whether the the exception is an error is an application decision // if we catch an exception we decide carefully which one to // throw onwards Throwable exceptionToRethrow = null; // if the exception came from a precall or postcall // we will rethrow it if (!inInvoke) { exceptionToRethrow = e; } // if the exception didn't come from precall or postcall then it // came from invoke // we will rethrow this exception if it is not a runtime // exception, but we must unwrap InvocationTargetExceptions else { if (!(e instanceof RuntimeException)) { exceptionToRethrow = e; } } try { if(listener != null) listener.postInvokeExceptionalReturn(token, method, null, e); } catch (Throwable f) { // we caught an exception from // postInvokeExceptionalReturn // if we haven't already chosen an exception to rethrow then // we will throw this exception if (exceptionToRethrow == null) { exceptionToRethrow = f; } } // if we made it this far without choosing an exception we // should throw e if (exceptionToRethrow == null) { exceptionToRethrow = e; } throw exceptionToRethrow; } } //...original method body </code> * * */ public abstract class AbstractWovenProxyMethodAdapter extends GeneratorAdapter { /** The type of a RuntimeException */ private static final Type RUNTIME_EX_TYPE = Type.getType(RuntimeException.class); private static final Type THROWABLE_TYPE = Type.getType(Throwable.class); /** The postInvoke method of an {@link InvocationListener} */ private static final Method POST_INVOKE_METHOD = getAsmMethodFromClass(InvocationListener.class, "postInvoke", Object.class, Object.class, java.lang.reflect.Method.class, Object.class); /** The postInvokeExceptionalReturn method of an {@link InvocationListener} */ private static final Method POST_INVOKE_EXCEPTIONAL_METHOD = getAsmMethodFromClass(InvocationListener.class, "postInvokeExceptionalReturn", Object.class, Object.class, java.lang.reflect.Method.class, Throwable.class); /** The preInvoke method of an {@link InvocationListener} */ private static final Method PRE_INVOKE_METHOD = getAsmMethodFromClass(InvocationListener.class, "preInvoke", Object.class, java.lang.reflect.Method.class, Object[].class); /** The name of the static field that stores our {@link java.lang.reflect.Method} */ private final String methodStaticFieldName; /** The current method */ protected final Method currentTransformMethod; /** The type of <code>this</code> */ protected final Type typeBeingWoven; /** True if this is a void method */ private final boolean isVoid; //ints for local store /** The local we use to store the {@link InvocationListener} token */ private int preInvokeReturnedToken; /** The local we use to note whether we are in the original method body or not */ private int inNormalMethod; /** The local we use to store the invocation target to dispatch to */ private int dispatchTarget; /** The local for storing our method's result */ private int normalResult; //the Labels we need for jumping around the pre/post/postexception and current method code /** This marks the start of the try/catch around the pre/postInvoke*/ private final Label beginTry = new Label(); /** This marks the end of the try/catch around the pre/postInvoke*/ private final Label endTry = new Label(); /** The return type of this method */ private final Type returnType; private final Type methodDeclaringType; private final boolean isMethodDeclaringTypeInterface; private boolean isDefaultMethod; /** * Construct a new method adapter * @param mv - the method visitor to write to * @param access - the access modifiers on this method * @param name - the name of this method * @param desc - the descriptor of this method * @param methodStaticFieldName - the name of the static field that will hold * the {@link java.lang.reflect.Method} representing * this method. * @param currentTransformMethod - the ASM representation of this method * @param proxyType - the type being woven that contains this method */ public AbstractWovenProxyMethodAdapter(MethodVisitor mv, int access, String name, String desc, String methodStaticFieldName, Method currentTransformMethod, Type typeBeingWoven, Type methodDeclaringType, boolean isMethodDeclaringTypeInterface, boolean isDefaultMethod) { super(ASM5, mv, access, name, desc); this.methodStaticFieldName = methodStaticFieldName; this.currentTransformMethod = currentTransformMethod; returnType = currentTransformMethod.getReturnType(); isVoid = returnType.getSort() == Type.VOID; this.typeBeingWoven = typeBeingWoven; this.methodDeclaringType = methodDeclaringType; this.isMethodDeclaringTypeInterface = isMethodDeclaringTypeInterface; this.isDefaultMethod = isDefaultMethod; } @Override public abstract void visitCode(); @Override public abstract void visitMaxs(int stack, int locals); /** * Write out the bytecode instructions necessary to do the dispatch. * We know the dispatcher is non-null, and we need a try/catch around the * invocation and listener calls. */ protected final void writeDispatcher() { // Setup locals we will use in the dispatch setupLocals(); //Write the try catch block visitTryCatchBlock(beginTry, endTry, endTry, THROWABLE_INAME); mark(beginTry); //Start dispatching, get the target object and store it loadThis(); getField(typeBeingWoven, DISPATCHER_FIELD, DISPATCHER_TYPE); invokeInterface(DISPATCHER_TYPE, new Method("call", OBJECT_TYPE, NO_ARGS)); storeLocal(dispatchTarget); //Pre-invoke, invoke, post-invoke, return writePreInvoke(); //Start the real method push(true); storeLocal(inNormalMethod); //Dispatch the method and store the result (null for void) loadLocal(dispatchTarget); checkCast(methodDeclaringType); loadArgs(); if(isMethodDeclaringTypeInterface && !isDefaultMethod) { invokeInterface(methodDeclaringType, currentTransformMethod); } else { invokeVirtual(methodDeclaringType, currentTransformMethod); } if(isVoid) { visitInsn(ACONST_NULL); } storeLocal(normalResult); // finish the real method and post-invoke push(false); storeLocal(inNormalMethod); writePostInvoke(); //Return, with the return value if necessary if(!!!isVoid) { loadLocal(normalResult); } returnValue(); //End of our try, start of our catch mark(endTry); writeMethodCatchHandler(); } /** * Setup the normalResult, inNormalMethod, preInvokeReturnedToken and * dispatch target locals. */ private final void setupLocals() { if (isVoid){ normalResult = newLocal(OBJECT_TYPE); } else{ normalResult = newLocal(returnType); } preInvokeReturnedToken = newLocal(OBJECT_TYPE); visitInsn(ACONST_NULL); storeLocal(preInvokeReturnedToken); inNormalMethod = newLocal(Type.BOOLEAN_TYPE); push(false); storeLocal(inNormalMethod); dispatchTarget = newLocal(OBJECT_TYPE); visitInsn(ACONST_NULL); storeLocal(dispatchTarget); } /** * Begin trying to invoke the listener, if the listener is * null the bytecode will branch to the supplied label, other * otherwise it will load the listener onto the stack. * @param l The label to branch to */ private final void beginListenerInvocation(Label l) { //If there's no listener then skip invocation loadThis(); getField(typeBeingWoven, LISTENER_FIELD, LISTENER_TYPE); ifNull(l); loadThis(); getField(typeBeingWoven, LISTENER_FIELD, LISTENER_TYPE); } /** * Write out the preInvoke. This copes with the listener being null */ private final void writePreInvoke() { //The place to go if the listener is null Label nullListener = newLabel(); beginListenerInvocation(nullListener); // The listener is on the stack, we need (target, method, args) loadLocal(dispatchTarget); getStatic(typeBeingWoven, methodStaticFieldName, METHOD_TYPE); loadArgArray(); //invoke it and store the token returned invokeInterface(LISTENER_TYPE, PRE_INVOKE_METHOD); storeLocal(preInvokeReturnedToken); mark(nullListener); } /** * Write out the postInvoke. This copes with the listener being null */ private final void writePostInvoke() { //The place to go if the listener is null Label nullListener = newLabel(); beginListenerInvocation(nullListener); // The listener is on the stack, we need (token, target, method, result) loadLocal(preInvokeReturnedToken); loadLocal(dispatchTarget); getStatic(typeBeingWoven, methodStaticFieldName, METHOD_TYPE); loadLocal(normalResult); //If the result a primitive then we need to box it if (!!!isVoid && returnType.getSort() != Type.OBJECT && returnType.getSort() != Type.ARRAY){ box(returnType); } //invoke the listener invokeInterface(LISTENER_TYPE, POST_INVOKE_METHOD); mark(nullListener); } /** * Write the catch handler for our method level catch, this runs the exceptional * post-invoke if there is a listener, and throws the correct exception at the * end */ private final void writeMethodCatchHandler() { //Store the original exception int originalException = newLocal(THROWABLE_TYPE); storeLocal(originalException); //Start by initialising exceptionToRethrow int exceptionToRethrow = newLocal(THROWABLE_TYPE); visitInsn(ACONST_NULL); storeLocal(exceptionToRethrow); //We need another try catch around the postInvokeExceptionalReturn, so here //are some labels and the declaration for it Label beforeInvoke = newLabel(); Label afterInvoke = newLabel(); visitTryCatchBlock(beforeInvoke, afterInvoke, afterInvoke, THROWABLE_INAME); //If we aren't in normal flow then set exceptionToRethrow = originalException loadLocal(inNormalMethod); Label inNormalMethodLabel = newLabel(); // Jump if not zero (false) visitJumpInsn(IFNE, inNormalMethodLabel); loadLocal(originalException); storeLocal(exceptionToRethrow); goTo(beforeInvoke); mark(inNormalMethodLabel); //We are in normal method flow so set exceptionToRethrow = originalException //if originalException is not a runtime exception loadLocal(originalException); instanceOf(RUNTIME_EX_TYPE); //If false then store original in toThrow, otherwise go to beforeInvoke visitJumpInsn(IFNE, beforeInvoke); loadLocal(originalException); storeLocal(exceptionToRethrow); goTo(beforeInvoke); //Setup of variables finished, begin try/catch //Mark the start of our try mark(beforeInvoke); //Begin invocation of the listener, jump to throw if null Label throwSelectedException = newLabel(); beginListenerInvocation(throwSelectedException); //We have a listener, so call it (token, target, method, exception) loadLocal(preInvokeReturnedToken); loadLocal(dispatchTarget); getStatic(typeBeingWoven, methodStaticFieldName, METHOD_TYPE); loadLocal(originalException); invokeInterface(LISTENER_TYPE, POST_INVOKE_EXCEPTIONAL_METHOD); goTo(throwSelectedException); mark(afterInvoke); //catching another exception replaces the original storeLocal(originalException); //Throw exceptionToRethrow if it isn't null, or the original if it is Label throwException = newLabel(); mark(throwSelectedException); loadLocal(exceptionToRethrow); ifNonNull(throwException); loadLocal(originalException); storeLocal(exceptionToRethrow); mark(throwException); loadLocal(exceptionToRethrow); throwException(); } /** * This method unwraps woven proxy instances for use in the right-hand side * of equals methods */ protected final void unwrapEqualsArgument() { //Create and initialize a local for our work int unwrapLocal = newLocal(OBJECT_TYPE); visitInsn(ACONST_NULL); storeLocal(unwrapLocal); Label startUnwrap = newLabel(); mark(startUnwrap); //Load arg and check if it is a WovenProxy instances loadArg(0); instanceOf(WOVEN_PROXY_IFACE_TYPE); Label unwrapFinished = newLabel(); //Jump if zero (false) visitJumpInsn(Opcodes.IFEQ, unwrapFinished); //Arg is a wovenProxy, if it is the same as last time then we're done loadLocal(unwrapLocal); loadArg(0); ifCmp(OBJECT_TYPE, EQ, unwrapFinished); //Not the same, store current arg in unwrapLocal for next loop loadArg(0); storeLocal(unwrapLocal); //So arg is a WovenProxy, but not the same as last time, cast it and store //the result of unwrap.call in the arg loadArg(0); checkCast(WOVEN_PROXY_IFACE_TYPE); //Now unwrap invokeInterface(WOVEN_PROXY_IFACE_TYPE, new Method("org_apache_aries_proxy_weaving_WovenProxy_unwrap", DISPATCHER_TYPE, NO_ARGS)); //Now we may have a Callable to invoke int callable = newLocal(DISPATCHER_TYPE); storeLocal(callable); loadLocal(callable); ifNull(unwrapFinished); loadLocal(callable); invokeInterface(DISPATCHER_TYPE, new Method("call", OBJECT_TYPE, NO_ARGS)); //Store the result and we're done (for this iteration) storeArg(0); goTo(startUnwrap); mark(unwrapFinished); } /** * A utility method for getting an ASM method from a Class * @param clazz the class to search * @param name The method name * @param argTypes The method args * @return */ private static final Method getAsmMethodFromClass(Class<?> clazz, String name, Class<?>... argTypes) { //get the java.lang.reflect.Method to get the types java.lang.reflect.Method ilMethod = null; try { ilMethod = clazz.getMethod(name, argTypes); } catch (Exception e) { //Should be impossible! throw new RuntimeException(format("Error finding InvocationListener method %s with argument types %s.", name, Arrays.toString(argTypes)), e); } //get the ASM method return new Method(name, Type.getReturnType(ilMethod), Type.getArgumentTypes(ilMethod)); } }