/************************************************************************************** * 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.MethodVisitor; import org.objectweb.asm.MethodAdapter; import org.objectweb.asm.Label; import org.objectweb.asm.Type; 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.MemberInfo; 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.ContextImpl; import org.codehaus.aspectwerkz.transform.inlining.AsmHelper; import org.codehaus.aspectwerkz.transform.inlining.EmittedJoinPoint; import java.util.Iterator; import java.util.Set; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.HashMap; import java.lang.reflect.Modifier; /** * Advises catch clauses by inserting a call to the join point as the first thing in the catch block. * * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a> */ public class HandlerVisitor extends ClassAdapter implements TransformationConstants { /** * A visitor that looks for all catch clause and keep track of them * providing that they match * * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a> */ public static class LookaheadCatchLabelsClassAdapter extends ClassAdapter { /** * list of CatchLabelStruct that matches */ List m_catchLabels = new ArrayList(); /** * map of Integer(index in whole class)-->asm.Label for all the visited labels */ private final Map m_labelIndexes = new HashMap(); /** * current label index in whole class, from 0 to N */ private int m_labelIndex = -1; private final ContextImpl m_ctx; private final ClassLoader m_loader; private final ClassInfo m_callerClassInfo; /** * Visit the class * * @param cv * @param loader * @param callerClassInfo * @param ctx * @param catchLabels */ public LookaheadCatchLabelsClassAdapter(ClassVisitor cv, ClassLoader loader, ClassInfo callerClassInfo, Context ctx, List catchLabels) { super(cv); m_catchLabels = catchLabels; m_loader = loader; m_callerClassInfo = callerClassInfo; m_ctx = (ContextImpl) ctx; } /** * Visit method bodies * * @param access * @param callerMethodName * @param callerMethodDesc * @param exceptions * @return */ public MethodVisitor visitMethod(final int access, final String callerMethodName, final String callerMethodDesc, final String callerMethodsignature, final String[] exceptions) { if (callerMethodName.startsWith(WRAPPER_METHOD_PREFIX)) { return super.visitMethod(access, callerMethodName, callerMethodDesc, callerMethodsignature, exceptions); } MethodVisitor mv = cv.visitMethod(access, callerMethodName, callerMethodDesc, callerMethodsignature, exceptions); if (mv == null) { return mv; } final MemberInfo callerMemberInfo; if (CLINIT_METHOD_NAME.equals(callerMethodName)) { callerMemberInfo = m_callerClassInfo.staticInitializer(); } else if (INIT_METHOD_NAME.equals(callerMethodName)) { int hash = AsmHelper.calculateConstructorHash(callerMethodDesc); callerMemberInfo = m_callerClassInfo.getConstructor(hash); } else { int hash = AsmHelper.calculateMethodHash(callerMethodName, callerMethodDesc); callerMemberInfo = m_callerClassInfo.getMethod(hash); } if (callerMemberInfo == null) { System.err.println( "AW::WARNING " + "metadata structure could not be build for method [" + m_callerClassInfo.getName().replace('/', '.') + '.' + callerMethodName + ':' + callerMethodDesc + ']' ); return mv; } /** * Visit the method, and keep track of all labels so that when visittryCatch is reached * we can remember the index * * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a> */ return new MethodAdapter(mv) { public void visitLabel(Label label) { m_labelIndexes.put(label, new Integer(++m_labelIndex)); super.visitLabel(label); } public void visitTryCatchBlock(Label startLabel, Label endLabel, Label handlerLabel, String exceptionTypeName) { if (exceptionTypeName == null) { // finally block super.visitTryCatchBlock(startLabel, endLabel, handlerLabel, exceptionTypeName); return; } final ClassInfo exceptionClassInfo = AsmClassInfo.getClassInfo(exceptionTypeName, m_loader); final ExpressionContext ctx = new ExpressionContext( PointcutType.HANDLER, exceptionClassInfo, callerMemberInfo ); if (!handlerFilter(m_ctx.getDefinitions(), ctx)) { // remember its index and the exception exceptionClassInfo Integer index = (Integer) m_labelIndexes.get(handlerLabel); if (index != null) { m_catchLabels.add( new CatchLabelStruct( index.intValue(), exceptionClassInfo, m_callerClassInfo, callerMemberInfo ) ); } } super.visitTryCatchBlock(startLabel, endLabel, handlerLabel, exceptionTypeName); } }; } } //---- non lookahead visitor private final ContextImpl m_ctx; /** * List of matching catch clause */ private final List m_catchLabels; /** * catch clause index in whole class */ private int m_labelIndex = -1; private Label m_lastLabelForLineNumber = EmittedJoinPoint.NO_LINE_NUMBER; /** * Creates a new instance. * * @param cv * @param ctx */ public HandlerVisitor(final ClassVisitor cv, final Context ctx, final List catchLabels) { super(cv); m_ctx = (ContextImpl) ctx; m_catchLabels = catchLabels; } /** * Visits the methods bodies to weave in JP calls at catch clauses * * @param access * @param name * @param desc * @param signature * @param exceptions * @return */ public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { if (name.startsWith(WRAPPER_METHOD_PREFIX)) { return super.visitMethod(access, name, desc, signature, exceptions); } MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); return mv == null ? null : new CatchClauseCodeAdapter(mv); } /** * Advises catch clauses by inserting a call to the join point as the first thing in the catch block. * * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a> */ public class CatchClauseCodeAdapter extends MethodAdapter { /** * Creates a new instance. * * @param ca */ public CatchClauseCodeAdapter(final MethodVisitor ca) { super(ca); } public void visitLabel(Label label) { m_lastLabelForLineNumber = label; super.visitLabel(label); // check if it is a catch label int index = ++m_labelIndex; CatchLabelStruct catchLabel = null; for (Iterator iterator = m_catchLabels.iterator(); iterator.hasNext();) { CatchLabelStruct aCatchLabel = (CatchLabelStruct) iterator.next(); if (aCatchLabel.labelIndexInWholeClass == index) { catchLabel = aCatchLabel; break; } } if (catchLabel == null) { return; } // matched m_ctx.markAsAdvised(); final String callerTypeName = catchLabel.caller.getName().replace('.', '/'); final String exceptionTypeDesc = catchLabel.exception.getSignature(); final String exceptionTypeName = Type.getType(exceptionTypeDesc).getInternalName(); final int joinPointHash = AsmHelper.calculateClassHash(exceptionTypeDesc); final String joinPointClassName = TransformationUtil.getJoinPointClassName( callerTypeName, catchLabel.callerMember.getName(), catchLabel.callerMember.getSignature(), exceptionTypeName, JoinPointType.HANDLER_INT, joinPointHash ); // add the call to the join point // exception instance is on the stack // dup it for ARG0 mv.visitInsn(DUP); // load caller instance if any if (Modifier.isStatic(catchLabel.callerMember.getModifiers())) { mv.visitInsn(ACONST_NULL); } else { mv.visitVarInsn(ALOAD, 0); } //TODO for now we pass the exception as both CALLEE and ARG0 - may be callee must be NULL //? check in AJ RTTI mv.visitMethodInsn( INVOKESTATIC, joinPointClassName, INVOKE_METHOD_NAME, TransformationUtil.getInvokeSignatureForHandlerJoinPoints(callerTypeName, exceptionTypeName) ); // emit the joinpoint m_ctx.addEmittedJoinPoint( new EmittedJoinPoint( JoinPointType.HANDLER_INT, callerTypeName, catchLabel.callerMember.getName(), catchLabel.callerMember.getSignature(), catchLabel.callerMember.getModifiers(), exceptionTypeName, "", exceptionTypeDesc, 0, // a bit meaningless but must not be static joinPointHash, joinPointClassName, m_lastLabelForLineNumber ) ); } } /** * Filters out the catch clauses that are not eligible for transformation. * * @param definitions * @param ctx * @return boolean true if the catch clause should be filtered out */ static boolean handlerFilter(final Set definitions, final ExpressionContext ctx) { for (Iterator it = definitions.iterator(); it.hasNext();) { if (((SystemDefinition) it.next()).hasPointcut(ctx)) { return false; } else { continue; } } return true; } /** * A struct to represent a catch clause. * The index is class wide, and the exception class info is kept. * * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a> */ private static class CatchLabelStruct { int labelIndexInWholeClass = -1; ClassInfo exception = null; ClassInfo caller = null; MemberInfo callerMember = null; private CatchLabelStruct(int indexInClass, ClassInfo exception, ClassInfo caller, MemberInfo callerMember) { labelIndexInWholeClass = indexInClass; this.exception = exception; this.caller = caller; this.callerMember = callerMember; } } }