/**************************************************************************************
* Copyright (c) Jonas Bon�r, Alexandre Vasseur. All rights reserved. *
* http://aspectwerkz.codehaus.org *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the LGPL license *
* a copy of which has been included with this distribution in the license.txt file. *
**************************************************************************************/
package org.codehaus.aspectwerkz.transform.inlining.weaver;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.CodeVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.CodeAdapter;
import org.objectweb.asm.Label;
import org.codehaus.aspectwerkz.definition.SystemDefinition;
import org.codehaus.aspectwerkz.expression.ExpressionContext;
import org.codehaus.aspectwerkz.expression.PointcutType;
import org.codehaus.aspectwerkz.joinpoint.management.JoinPointType;
import org.codehaus.aspectwerkz.reflect.ClassInfo;
import org.codehaus.aspectwerkz.reflect.MethodInfo;
import org.codehaus.aspectwerkz.reflect.MemberInfo;
import org.codehaus.aspectwerkz.reflect.ClassInfoHelper;
import org.codehaus.aspectwerkz.reflect.impl.asm.AsmClassInfo;
import org.codehaus.aspectwerkz.transform.Context;
import org.codehaus.aspectwerkz.transform.TransformationUtil;
import org.codehaus.aspectwerkz.transform.TransformationConstants;
import org.codehaus.aspectwerkz.transform.inlining.compiler.AbstractJoinPointCompiler;
import org.codehaus.aspectwerkz.transform.inlining.ContextImpl;
import org.codehaus.aspectwerkz.transform.inlining.AsmHelper;
import org.codehaus.aspectwerkz.transform.inlining.EmittedJoinPoint;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.Set;
/**
* Instruments method CALL join points by replacing INVOKEXXX instructions with invocations of the compiled join point.
* <br/>
* It calls the JPClass.invoke static method. The signature of the invoke method depends if the
* target method is static or not as follow:
* <pre>
* invoke(callee, args.., caller) // non static
* invoke(args.., caller) // static
* </pre>
* (The reason why is that it simplifies call pointcut stack management)
*
* @author <a href="mailto:jboner@codehaus.org">Jonas Bon�r </a>
* @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
*/
public class MethodCallVisitor extends ClassAdapter implements TransformationConstants {
private final ContextImpl m_ctx;
private final ClassLoader m_loader;
private final ClassInfo m_callerClassInfo;
private Label m_lastLabelForLineNumber = EmittedJoinPoint.NO_LINE_NUMBER;
/**
* Creates a new instance.
*
* @param cv
* @param loader
* @param classInfo
* @param ctx
*/
public MethodCallVisitor(final ClassVisitor cv,
final ClassLoader loader,
final ClassInfo classInfo,
final Context ctx) {
super(cv);
m_loader = loader;
m_callerClassInfo = classInfo;
m_ctx = (ContextImpl) ctx;
}
/**
* Visits the caller methods.
*
* @param access
* @param name
* @param desc
* @param exceptions
* @param attrs
* @return
*/
public CodeVisitor visitMethod(final int access,
final String name,
final String desc,
final String[] exceptions,
final Attribute attrs) {
if (name.startsWith(WRAPPER_METHOD_PREFIX) ||
Modifier.isNative(access) ||
Modifier.isAbstract(access)) {
return super.visitMethod(access, name, desc, exceptions, attrs);
}
CodeVisitor mv = cv.visitMethod(access, name, desc, exceptions, attrs);
return mv == null ? null : new ReplaceInvokeInstructionCodeAdapter(
mv,
m_loader,
m_callerClassInfo,
m_ctx.getClassName(),
name,
desc
);
}
/**
* Replaces 'INVOKEXXX' instructions with a call to the compiled JoinPoint instance.
*
* @author <a href="mailto:jboner@codehaus.org">Jonas Bon�r </a>
* @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
*/
public class ReplaceInvokeInstructionCodeAdapter extends AfterObjectInitializationCodeAdapter {
private final ClassLoader m_loader;
private final ClassInfo m_callerClassInfo;
private final String m_callerClassName;
private final String m_callerMethodName;
private final String m_callerMethodDesc;
private final MemberInfo m_callerMemberInfo;
/**
* Creates a new instance.
*
* @param ca
* @param loader
* @param callerClassInfo
* @param callerClassName
* @param callerMethodName
* @param callerMethodDesc
*/
public ReplaceInvokeInstructionCodeAdapter(final CodeVisitor ca,
final ClassLoader loader,
final ClassInfo callerClassInfo,
final String callerClassName,
final String callerMethodName,
final String callerMethodDesc) {
super(ca, callerMethodName);
m_loader = loader;
m_callerClassInfo = callerClassInfo;
m_callerClassName = callerClassName;
m_callerMethodName = callerMethodName;
m_callerMethodDesc = callerMethodDesc;
if (CLINIT_METHOD_NAME.equals(callerMethodName)) {
m_callerMemberInfo = m_callerClassInfo.staticInitializer();
} else if (INIT_METHOD_NAME.equals(callerMethodName)) {
int hash = AsmHelper.calculateConstructorHash(m_callerMethodDesc);
m_callerMemberInfo = m_callerClassInfo.getConstructor(hash);
} else {
int hash = AsmHelper.calculateMethodHash(m_callerMethodName, m_callerMethodDesc);
m_callerMemberInfo = m_callerClassInfo.getMethod(hash);
}
}
/**
* Label
*
* @param label
*/
public void visitLabel(Label label) {
m_lastLabelForLineNumber = label;
super.visitLabel(label);
}
/**
* Visits 'INVOKEXXX' instructions.
*
* @param opcode
* @param calleeClassName
* @param calleeMethodName
* @param calleeMethodDesc
*/
public void visitMethodInsn(final int opcode,
String calleeClassName,
final String calleeMethodName,
final String calleeMethodDesc) {
if (m_callerMemberInfo == null) {
System.err.println(
"AW::WARNING " +
"metadata structure could not be build for method ["
+ m_callerClassInfo.getName().replace('/', '.')
+ '.' + m_callerMethodName + ':' + m_callerMethodDesc + ']'
);
super.visitMethodInsn(opcode, calleeClassName, calleeMethodName, calleeMethodDesc);
return;
}
if (INIT_METHOD_NAME.equals(calleeMethodName) ||
CLINIT_METHOD_NAME.equals(calleeMethodName) ||
calleeMethodName.startsWith(ASPECTWERKZ_PREFIX)
|| calleeClassName.endsWith(JOIN_POINT_CLASS_SUFFIX)
//calleeClassName.startsWith("org/aopalliance/")) { // FIXME make generic fix by invoking all AspectModels (same problem in other visitors as well)
){super.visitMethodInsn(opcode, calleeClassName, calleeMethodName, calleeMethodDesc);
return;
}
// check if we have a super.sameMethod() call
if (opcode == INVOKESPECIAL
&& !calleeClassName.equals(m_callerClassName)
&& ClassInfoHelper.extendsSuperClass(m_callerClassInfo, calleeClassName.replace('/', '.'))) {
super.visitMethodInsn(opcode, calleeClassName, calleeMethodName, calleeMethodDesc);
return;
}
// check if object initialization has been reached
if (!m_isObjectInitialized) {
super.visitMethodInsn(opcode, calleeClassName, calleeMethodName, calleeMethodDesc);
return;
}
int joinPointHash = AsmHelper.calculateMethodHash(calleeMethodName, calleeMethodDesc);
ClassInfo classInfo = AsmClassInfo.getClassInfo(calleeClassName, m_loader);
MethodInfo calleeMethodInfo = classInfo.getMethod(joinPointHash);
if (calleeMethodInfo == null) {
System.err.println(
"AW::WARNING " +
"metadata structure could not be build for method ["
+ classInfo.getName().replace('/', '.')
+ '.' + calleeMethodName + ':' + calleeMethodDesc
+ "] when parsing method ["
+ m_callerClassInfo.getName() + '.' + m_callerMethodName + "(..)]"
);
// bail out
super.visitMethodInsn(opcode, calleeClassName, calleeMethodName, calleeMethodDesc);
return;
}
ExpressionContext ctx = new ExpressionContext(PointcutType.CALL, calleeMethodInfo, m_callerMemberInfo);
if (methodFilter(m_ctx.getDefinitions(), ctx, calleeMethodInfo)) {
super.visitMethodInsn(opcode, calleeClassName, calleeMethodName, calleeMethodDesc);
} else {
m_ctx.markAsAdvised();
String joinPointClassName = TransformationUtil.getJoinPointClassName(
m_callerClassName,
m_callerMethodName,
m_callerMethodDesc,
calleeClassName,
JoinPointType.METHOD_CALL_INT,
joinPointHash
);
// load the caller instance (this), or null if in a static context
// note that callee instance [optional] and args are already on the stack
if (Modifier.isStatic(m_callerMemberInfo.getModifiers())) {
visitInsn(ACONST_NULL);
} else {
visitVarInsn(ALOAD, 0);
}
// add the call to the join point
super.visitMethodInsn(
INVOKESTATIC,
joinPointClassName,
INVOKE_METHOD_NAME,
TransformationUtil.getInvokeSignatureForCodeJoinPoints(
calleeMethodInfo.getModifiers(), calleeMethodDesc,
m_callerClassName, calleeClassName
)
);
// emit the joinpoint
//See AW-253 - we remember if we had an INVOKE INTERFACE opcode
int modifiers = calleeMethodInfo.getModifiers();
if (opcode == INVOKEINTERFACE) {
modifiers = modifiers | MODIFIER_INVOKEINTERFACE;
}
m_ctx.addEmittedJoinPoint(
new EmittedJoinPoint(
JoinPointType.METHOD_CALL_INT,
m_callerClassName,
m_callerMethodName,
m_callerMethodDesc,
m_callerMemberInfo.getModifiers(),
calleeClassName,
calleeMethodName,
calleeMethodDesc,
modifiers,
joinPointHash,
joinPointClassName,
m_lastLabelForLineNumber
)
);
}
}
/**
* Filters out the methods that are not eligible for transformation.
* Do not filter on abstract callee method - needed for interface declared method call
* (invokeinterface instr.)
*
* @param definitions
* @param ctx
* @param calleeMethodInfo
* @return boolean true if the method should be filtered out
*/
public boolean methodFilter(final Set definitions,
final ExpressionContext ctx,
final MethodInfo calleeMethodInfo) {
if (calleeMethodInfo.getName().equals(INIT_METHOD_NAME) ||
calleeMethodInfo.getName().equals(CLINIT_METHOD_NAME) ||
calleeMethodInfo.getName().startsWith(ORIGINAL_METHOD_PREFIX)) {
return true;
}
for (Iterator it = definitions.iterator(); it.hasNext();) {
if (((SystemDefinition) it.next()).hasPointcut(ctx)) {
return false;
} else {
continue;
}
}
return true;
}
}
}