blob: cd518adced2798d7457c7c005f3956fea71c6a14 [file] [log] [blame]
/*
* 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.*;
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(ASM9, 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) {
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));
}
}