View Javadoc

    1   /***************************************************************************************
    2    * Copyright (c) Jonas BonŽr, Alexandre Vasseur. All rights reserved.                 *
    3    * http://aspectwerkz.codehaus.org                                                    *
    4    * ---------------------------------------------------------------------------------- *
    5    * The software in this package is published under the terms of the LGPL license      *
    6    * a copy of which has been included with this distribution in the license.txt file.  *
    7    **************************************************************************************/
    8   package org.codehaus.aspectwerkz.transform.inlining.weaver;
    9
    10  import org.objectweb.asm.ClassAdapter;
    11  import org.objectweb.asm.ClassVisitor;
    12  import org.objectweb.asm.CodeVisitor;
    13  import org.objectweb.asm.Attribute;
    14  import org.objectweb.asm.Constants;
    15  import org.objectweb.asm.CodeAdapter;
    16  import org.objectweb.asm.Label;
    17  import org.codehaus.aspectwerkz.definition.SystemDefinition;
    18  import org.codehaus.aspectwerkz.expression.ExpressionContext;
    19  import org.codehaus.aspectwerkz.expression.PointcutType;
    20  import org.codehaus.aspectwerkz.joinpoint.management.JoinPointType;
    21  import org.codehaus.aspectwerkz.reflect.ClassInfo;
    22  import org.codehaus.aspectwerkz.reflect.ConstructorInfo;
    23  import org.codehaus.aspectwerkz.reflect.MemberInfo;
    24  import org.codehaus.aspectwerkz.reflect.impl.asm.AsmClassInfo;
    25  import org.codehaus.aspectwerkz.transform.Context;
    26  import org.codehaus.aspectwerkz.transform.TransformationUtil;
    27  import org.codehaus.aspectwerkz.transform.TransformationConstants;
    28  import org.codehaus.aspectwerkz.transform.inlining.compiler.AbstractJoinPointCompiler;
    29  import org.codehaus.aspectwerkz.transform.inlining.ContextImpl;
    30  import org.codehaus.aspectwerkz.transform.inlining.AsmHelper;
    31  import org.codehaus.aspectwerkz.transform.inlining.EmittedJoinPoint;
    32  import org.codehaus.aspectwerkz.annotation.instrumentation.asm.AsmAnnotationHelper;
    33
    34  import java.lang.reflect.Modifier;
    35  import java.util.Iterator;
    36  import java.util.List;
    37  import java.util.Stack;
    38  import java.util.Set;
    39
    40  import gnu.trove.TLongObjectHashMap;
    41  import gnu.trove.TIntObjectHashMap;
    42
    43  /***
    44   * Instruments ctor CALL join points by replacing INVOKEXXX instructions with invocations of the compiled join point.
    45   * <br/>
    46   * It calls the JPClass.invoke static method. The signature of the invoke method is:
    47   * <pre>
    48   *      invoke(args.., caller) - note: no callee as arg0
    49   * </pre>
    50   * (The reason why is that it simplifies call pointcut stack management)
    51   *
    52   * <p/>
    53   * Note: The Eclipse compiler is generating "catch(exception) NEW DUP_X1 SWAP getMessage newError(..)"
    54   * hence NEW DUP_X1 is a valid sequence as well, and DUP_X1 is replaced by DUP to preserved the SWAP.
    55   * Other more complex schemes (DUP_X2) are not implemented (no real test so far)
    56   *
    57   * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
    58   */
    59  public class ConstructorCallVisitor extends ClassAdapter implements TransformationConstants {
    60
    61      private final static TIntObjectHashMap EMPTY_INTHASHMAP = new TIntObjectHashMap(0);
    62
    63      private final ContextImpl m_ctx;
    64      private final ClassLoader m_loader;
    65      private final ClassInfo m_callerClassInfo;
    66
    67      /***
    68       * Map of NEW instructions.
    69       * The key is the method (withincode) hash
    70       * The value is a TLongObjectHashMap whose key is index of NEW instructions and value instance of NewInvocationStruct
    71       */
    72      private final TLongObjectHashMap m_newInvocationsByCallerMemberHash;
    73
    74      private Label m_lastLabelForLineNumber = EmittedJoinPoint.NO_LINE_NUMBER;
    75
    76      /***
    77       * Creates a new instance.
    78       *
    79       * @param cv
    80       * @param loader
    81       * @param classInfo
    82       * @param ctx
    83       */
    84      public ConstructorCallVisitor(final ClassVisitor cv,
    85                                    final ClassLoader loader,
    86                                    final ClassInfo classInfo,
    87                                    final Context ctx,
    88                                    final TLongObjectHashMap newInvocationsByCallerMemberHash) {
    89          super(cv);
    90          m_loader = loader;
    91          m_callerClassInfo = classInfo;
    92          m_ctx = (ContextImpl) ctx;
    93          m_newInvocationsByCallerMemberHash = newInvocationsByCallerMemberHash;
    94      }
    95
    96      /***
    97       * Visits the caller methods.
    98       *
    99       * @param access
    100      * @param name
    101      * @param desc
    102      * @param exceptions
    103      * @param attrs
    104      * @return
    105      */
    106     public CodeVisitor visitMethod(final int access,
    107                                    final String name,
    108                                    final String desc,
    109                                    final String[] exceptions,
    110                                    final Attribute attrs) {
    111
    112         if (name.startsWith(WRAPPER_METHOD_PREFIX) ||
    113             Modifier.isNative(access) ||
    114             Modifier.isAbstract(access)) {
    115             return super.visitMethod(access, name, desc, exceptions, attrs);
    116         }
    117
    118         CodeVisitor mv = cv.visitMethod(access, name, desc, exceptions, attrs);
    119         return mv == null ? null : new ReplaceNewInstructionCodeAdapter(
    120                 mv,
    121                 m_loader,
    122                 m_callerClassInfo,
    123                 m_ctx.getClassName(),
    124                 name,
    125                 desc,
    126                 (TIntObjectHashMap) m_newInvocationsByCallerMemberHash.get(getMemberHash(name, desc))
    127         );
    128     }
    129
    130
    131     /***
    132      * Replaces 'new' instructions with a call to the compiled JoinPoint instance.
    133      * <br/>
    134      * It does the following:
    135      * - remove NEW <class> when we know (from first visit) that it matchs
    136      * - remove DUP that follows NEW <class>
    137      * - replace INVOKESPECIAL <ctor signature> with call to JP
    138      *
    139      * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
    140      */
    141     public class ReplaceNewInstructionCodeAdapter extends CodeAdapter {
    142
    143         private final ClassLoader m_loader;
    144         private final ClassInfo m_callerClassInfo;
    145         private final String m_callerClassName;
    146         private final String m_callerMethodName;
    147         private final String m_callerMethodDesc;
    148         private final MemberInfo m_callerMemberInfo;
    149
    150         /***
    151          * Map of NewInvocationStruct indexed by NEW indexes (incremented thru the visit) for the visited member code body
    152          */
    153         private final TIntObjectHashMap m_newInvocations;
    154
    155         /***
    156          * Index of NEW instr. in the scope of the visited member code body
    157          */
    158         private int m_newInvocationIndex = -1;
    159
    160         /***
    161          * Stack of NewInovationStruct, which mirrors the corresponding INVOKESPECIAL <init> when a NEW has been visited.
    162          * If the entry is NULL, it means that this ctor call does not match.
    163          * This allow to compute the match only once when the NEW is visited (since we have data from the first visit)
    164          * while supporting nested interception like new Foo(new Bar("s"))
    165          */
    166         private final Stack m_newInvocationStructStack = new Stack();
    167
    168         /***
    169          * Flag set to true just after a NEW that match has been visited
    170          */
    171         private boolean m_skipNextDup = false;
    172
    173         /***
    174          * Creates a new instance.
    175          *
    176          * @param ca
    177          * @param loader
    178          * @param callerClassInfo
    179          * @param callerClassName
    180          * @param callerMethodName
    181          * @param callerMethodDesc
    182          */
    183         public ReplaceNewInstructionCodeAdapter(final CodeVisitor ca,
    184                                                 final ClassLoader loader,
    185                                                 final ClassInfo callerClassInfo,
    186                                                 final String callerClassName,
    187                                                 final String callerMethodName,
    188                                                 final String callerMethodDesc,
    189                                                 final TIntObjectHashMap newInvocations) {
    190             super(ca);
    191             m_loader = loader;
    192             m_callerClassInfo = callerClassInfo;
    193             m_callerClassName = callerClassName;
    194             m_callerMethodName = callerMethodName;
    195             m_callerMethodDesc = callerMethodDesc;
    196             m_newInvocations = (newInvocations != null) ? newInvocations : EMPTY_INTHASHMAP;
    197
    198             if (CLINIT_METHOD_NAME.equals(m_callerMethodName)) {
    199                 m_callerMemberInfo = m_callerClassInfo.staticInitializer();
    200             } else if (INIT_METHOD_NAME.equals(m_callerMethodName)) {
    201                 final int hash = AsmHelper.calculateConstructorHash(m_callerMethodDesc);
    202                 m_callerMemberInfo = m_callerClassInfo.getConstructor(hash);
    203             } else {
    204                 final int hash = AsmHelper.calculateMethodHash(m_callerMethodName, m_callerMethodDesc);
    205                 m_callerMemberInfo = m_callerClassInfo.getMethod(hash);
    206             }
    207             if (m_callerMemberInfo == null) {
    208                 System.err.println(
    209                         "AW::WARNING " +
    210                         "metadata structure could not be build for method ["
    211                         + m_callerClassInfo.getName().replace('/', '.')
    212                         + '.' + m_callerMethodName + ':' + m_callerMethodDesc + ']'
    213                 );
    214             }
    215         }
    216
    217         /***
    218          * Label
    219          *
    220          * @param label
    221          */
    222         public void visitLabel(Label label) {
    223             m_lastLabelForLineNumber = label;
    224             super.visitLabel(label);
    225         }
    226
    227         /***
    228          * Removes the NEW when we know that the corresponding INVOKE SPECIAL <init> is advised.
    229          *
    230          * @param opcode
    231          * @param desc
    232          */
    233         public void visitTypeInsn(int opcode, String desc) {
    234             if (m_callerMemberInfo == null) {
    235                 return;
    236             }
    237
    238             if (opcode == NEW) {
    239                 m_newInvocationIndex++;
    240                 // build the callee ConstructorInfo and check for a match
    241                 NewInvocationStruct newInvocationStruct = (NewInvocationStruct) m_newInvocations.get(
    242                         m_newInvocationIndex
    243                 );
    244                 if (newInvocationStruct == null) {
    245                     super.visitTypeInsn(opcode, desc);//we failed
    246                     return;
    247                 }
    248                 String calleeClassName = newInvocationStruct.className;
    249                 String calleeMethodName = INIT_METHOD_NAME;
    250                 String calleeMethodDesc = newInvocationStruct.ctorDesc;
    251                 int joinPointHash = AsmHelper.calculateMethodHash(calleeMethodName, calleeMethodDesc);
    252                 ClassInfo classInfo = AsmClassInfo.getClassInfo(calleeClassName, m_loader);
    253                 ConstructorInfo calleeConstructorInfo = classInfo.getConstructor(joinPointHash);
    254                 if (calleeConstructorInfo == null) {
    255                     super.visitTypeInsn(opcode, desc);//we failed
    256                     System.err.println(
    257                             "AW::WARNING " +
    258                             "metadata structure could not be build for method ["
    259                             + classInfo.getName().replace('/', '.')
    260                             + '.' + calleeMethodName + ':' + calleeMethodDesc + ']'
    261                     );
    262                     return;
    263                 }
    264
    265                 // do we have a match - if so, skip the NEW and the DUP
    266                 ExpressionContext ctx = new ExpressionContext(
    267                         PointcutType.CALL, calleeConstructorInfo, m_callerMemberInfo
    268                 );
    269                 if (constructorFilter(m_ctx.getDefinitions(), ctx, calleeConstructorInfo)) {
    270                     // push NULL as a struct (means no match)
    271                     m_newInvocationStructStack.push(null);
    272                     super.visitTypeInsn(opcode, desc);
    273                 } else {
    274                     // keep track of the ConstructorInfo so that we don't compute it again in visitMethodInsn <init>
    275                     newInvocationStruct.constructorInfo = calleeConstructorInfo;
    276                     newInvocationStruct.joinPointHash = joinPointHash;
    277                     m_newInvocationStructStack.push(newInvocationStruct);
    278                     // skip NEW instr and flag to skip next DUP
    279                     m_skipNextDup = true;
    280                     //System.out.println("RECORD " + calleeClassName + calleeMethodDesc);
    281                 }
    282             } else {
    283                 // is not a NEW instr
    284                 super.visitTypeInsn(opcode, desc);
    285             }
    286         }
    287
    288         /***
    289          * Remove the DUP instruction if we know that those were for a NEW ... INVOKESPECIAL that match.
    290          *
    291          * @param opcode
    292          */
    293         public void visitInsn(int opcode) {
    294             if ((opcode == DUP || opcode == DUP_X1) && m_skipNextDup) {
    295                 //System.out.println("SKIP dup");
    296                 ;// skip the DUP
    297                 if (opcode == DUP_X1)
    298                     super.visitInsn(DUP);
    299             } else {
    300                 super.visitInsn(opcode);
    301             }
    302             m_skipNextDup = false;
    303         }
    304
    305         /***
    306          * Visits INVOKESPECIAL <init> instructions and replace them with a call to the join point when matched.
    307          *
    308          * @param opcode
    309          * @param calleeClassName
    310          * @param calleeConstructorName
    311          * @param calleeConstructorDesc
    312          */
    313         public void visitMethodInsn(final int opcode,
    314                                     final String calleeClassName,
    315                                     final String calleeConstructorName,
    316                                     final String calleeConstructorDesc) {
    317
    318             if (m_callerMemberInfo == null) {
    319                 super.visitMethodInsn(opcode, calleeClassName, calleeConstructorName, calleeConstructorDesc);
    320                 return;
    321             }
    322
    323             if (!INIT_METHOD_NAME.equals(calleeConstructorName) ||
    324                     calleeClassName.endsWith(AbstractJoinPointCompiler.JOIN_POINT_CLASS_SUFFIX)) {
    325                 super.visitMethodInsn(opcode, calleeClassName, calleeConstructorName, calleeConstructorDesc);
    326                 return;
    327             }
    328
    329             // get the info from the invocation stack since all the matching has already been done
    330             if (m_newInvocationStructStack.isEmpty()) {
    331                 // nothing to weave
    332                 super.visitMethodInsn(opcode, calleeClassName, calleeConstructorName, calleeConstructorDesc);
    333                 return;
    334             }
    335
    336             NewInvocationStruct struct = (NewInvocationStruct) m_newInvocationStructStack.pop();
    337             if (struct == null) {
    338                 // not matched
    339                 super.visitMethodInsn(opcode, calleeClassName, calleeConstructorName, calleeConstructorDesc);
    340             } else {
    341                 m_ctx.markAsAdvised();
    342
    343                 String joinPointClassName = TransformationUtil.getJoinPointClassName(
    344                         m_callerClassName,
    345                         m_callerMethodName,
    346                         m_callerMethodDesc,
    347                         calleeClassName,
    348                         JoinPointType.CONSTRUCTOR_CALL_INT,
    349                         struct.joinPointHash
    350                 );
    351
    352                 // load the caller instance (this), or null if in a static context
    353                 // note that callee instance [mandatory since ctor] and args are already on the stack
    354                 if (Modifier.isStatic(m_callerMemberInfo.getModifiers())) {
    355                     visitInsn(ACONST_NULL);
    356                 } else {
    357                     visitVarInsn(ALOAD, 0);
    358                 }
    359
    360                 // add the call to the join point
    361                 super.visitMethodInsn(
    362                         INVOKESTATIC,
    363                         joinPointClassName,
    364                         INVOKE_METHOD_NAME,
    365                         TransformationUtil.getInvokeSignatureForConstructorCallJoinPoints(
    366                                 calleeConstructorDesc,
    367                                 m_callerClassName,
    368                                 calleeClassName
    369                         )
    370                 );
    371
    372                 // emit the joinpoint
    373                 m_ctx.addEmittedJoinPoint(
    374                         new EmittedJoinPoint(
    375                                 JoinPointType.CONSTRUCTOR_CALL_INT,
    376                                 m_callerClassName,
    377                                 m_callerMethodName,
    378                                 m_callerMethodDesc,
    379                                 m_callerMemberInfo.getModifiers(),
    380                                 calleeClassName,
    381                                 calleeConstructorName,
    382                                 calleeConstructorDesc,
    383                                 struct.constructorInfo.getModifiers(),
    384                                 struct.joinPointHash,
    385                                 joinPointClassName,
    386                                 m_lastLabelForLineNumber
    387                         )
    388                 );
    389             }
    390         }
    391
    392         /***
    393          * Filters out the ctor that are not eligible for transformation.
    394          *
    395          * @param definitions
    396          * @param ctx
    397          * @param calleeConstructorInfo
    398          * @return boolean true if the method should be filtered out
    399          */
    400         public boolean constructorFilter(final Set definitions,
    401                                          final ExpressionContext ctx,
    402                                          final ConstructorInfo calleeConstructorInfo) {
    403             for (Iterator it = definitions.iterator(); it.hasNext();) {
    404                 if (((SystemDefinition) it.next()).hasPointcut(ctx)) {
    405                     return false;
    406                 } else {
    407                     continue;
    408                 }
    409             }
    410             return true;
    411         }
    412     }
    413
    414     private static int getMemberHash(String name, String desc) {
    415         int hash = 29;
    416         hash = (29 * hash) + name.hashCode();
    417         return (29 * hash) + desc.hashCode();
    418     }
    419
    420     /***
    421      * Lookahead index of NEW instruction for NEW + DUP + INVOKESPECIAL instructions
    422      * Remember the NEW instruction index
    423      * <p/>
    424      * Special case when withincode ctor of called ctor:
    425      * <pre>public Foo() { super(new Foo()); }</pre>
    426      * In such a case, it is not possible to intercept the call to new Foo() since this cannot be
    427      * referenced as long as this(..) or super(..) has not been called.
    428      *
    429      * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
    430      */
    431     public static class LookaheadNewDupInvokeSpecialInstructionClassAdapter
    432             extends AsmAnnotationHelper.NullClassAdapter {
    433
    434         private String m_callerMemberName;
    435
    436         // list of new invocations by caller member hash
    437         public TLongObjectHashMap m_newInvocationsByCallerMemberHash;
    438
    439         public LookaheadNewDupInvokeSpecialInstructionClassAdapter(TLongObjectHashMap newInvocations) {
    440             m_newInvocationsByCallerMemberHash = newInvocations;
    441         }
    442
    443         public CodeVisitor visitMethod(final int access,
    444                                        final String name,
    445                                        final String desc,
    446                                        final String[] exceptions,
    447                                        final Attribute attrs) {
    448             if (name.startsWith(WRAPPER_METHOD_PREFIX) ||
    449                 Modifier.isNative(access) ||
    450                 Modifier.isAbstract(access)) {
    451                 ;//ignore
    452             }
    453
    454             m_callerMemberName = name;
    455
    456             TIntObjectHashMap newInvocations = new TIntObjectHashMap(5);
    457             m_newInvocationsByCallerMemberHash.put(getMemberHash(name, desc), newInvocations);
    458             return new LookaheadNewDupInvokeSpecialInstructionCodeAdapter(
    459                     super.visitMethod(access, name, desc, exceptions, attrs),
    460                     newInvocations,
    461                     m_callerMemberName
    462             );
    463         }
    464     }
    465
    466     public static class LookaheadNewDupInvokeSpecialInstructionCodeAdapter
    467             extends AfterObjectInitializationCodeAdapter {
    468
    469         private TIntObjectHashMap m_newInvocations;
    470
    471         private Stack m_newIndexStack = new Stack();
    472         private int m_newIndex = -1;
    473
    474         /***
    475          * Creates a new instance.
    476          */
    477         public LookaheadNewDupInvokeSpecialInstructionCodeAdapter(CodeVisitor cv, TIntObjectHashMap newInvocations,
    478                                                                   final String callerMemberName) {
    479             super(cv, callerMemberName);
    480             m_newInvocations = newInvocations;
    481         }
    482
    483         public void visitTypeInsn(int opcode, String desc) {
    484             // make sure to call super first to compute post object initialization flag
    485             super.visitTypeInsn(opcode, desc);
    486             if (opcode == NEW) {
    487                 m_newIndex++;
    488                 m_newIndexStack.push(new Integer(m_newIndex));
    489             }
    490         }
    491
    492         public void visitMethodInsn(final int opcode,
    493                                     final String calleeClassName,
    494                                     final String calleeMethodName,
    495                                     final String calleeMethodDesc) {
    496             // make sure to call super first to compute post object initialization flag
    497             super.visitMethodInsn(opcode, calleeClassName, calleeMethodName, calleeMethodDesc);
    498
    499             if (INIT_METHOD_NAME.equals(calleeMethodName) && opcode == INVOKESPECIAL) {
    500                 if (!m_isObjectInitialized) {
    501                     // skip - remove the NEW index from the stack
    502                     if (!m_newIndexStack.isEmpty()) {
    503                         m_newIndexStack.pop();
    504                     }
    505                 } else {
    506                     if (!m_newIndexStack.isEmpty()) {
    507                         int index = ((Integer) m_newIndexStack.pop()).intValue();
    508                         NewInvocationStruct newInvocationStruct = new NewInvocationStruct();
    509                         newInvocationStruct.className = calleeClassName;
    510                         newInvocationStruct.ctorDesc = calleeMethodDesc;
    511                         // constructorInfo and matching will be done at weave time and not at lookahead time
    512                         m_newInvocations.put(index, newInvocationStruct);
    513                     }
    514                 }
    515             }
    516         }
    517     }
    518
    519     private static class NewInvocationStruct {
    520         public String className;
    521         public String ctorDesc;
    522         public ConstructorInfo constructorInfo = null;
    523         public int joinPointHash = -1;
    524     }
    525
    526 }