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 8of 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;
    9
    10  import gnu.trove.TLongObjectHashMap;
    11
    12  import java.util.ArrayList;
    13  import java.util.HashSet;
    14  import java.util.Iterator;
    15  import java.util.List;
    16  import java.util.Set;
    17
    18  import org.codehaus.aspectwerkz.definition.SystemDefinition;
    19  import org.codehaus.aspectwerkz.exception.WrappedRuntimeException;
    20  import org.codehaus.aspectwerkz.expression.ExpressionContext;
    21  import org.codehaus.aspectwerkz.expression.PointcutType;
    22  import org.codehaus.aspectwerkz.reflect.ClassInfo;
    23  import org.codehaus.aspectwerkz.reflect.impl.asm.AsmClassInfo;
    24  import org.codehaus.aspectwerkz.transform.Context;
    25  import org.codehaus.aspectwerkz.transform.WeavingStrategy;
    26  import org.codehaus.aspectwerkz.transform.inlining.weaver.AddInterfaceVisitor;
    27  import org.codehaus.aspectwerkz.transform.inlining.weaver.AddMixinMethodsVisitor;
    28  import org.codehaus.aspectwerkz.transform.inlining.weaver.AlreadyAddedMethodAdapter;
    29  import org.codehaus.aspectwerkz.transform.inlining.weaver.ConstructorBodyVisitor;
    30  import org.codehaus.aspectwerkz.transform.inlining.weaver.ConstructorCallVisitor;
    31  import org.codehaus.aspectwerkz.transform.inlining.weaver.FieldSetFieldGetVisitor;
    32  import org.codehaus.aspectwerkz.transform.inlining.weaver.HandlerVisitor;
    33  import org.codehaus.aspectwerkz.transform.inlining.weaver.InstanceLevelAspectVisitor;
    34  import org.codehaus.aspectwerkz.transform.inlining.weaver.JoinPointInitVisitor;
    35  import org.codehaus.aspectwerkz.transform.inlining.weaver.LabelToLineNumberVisitor;
    36  import org.codehaus.aspectwerkz.transform.inlining.weaver.MethodCallVisitor;
    37  import org.codehaus.aspectwerkz.transform.inlining.weaver.MethodExecutionVisitor;
    38  import org.codehaus.aspectwerkz.transform.inlining.weaver.StaticInitializationVisitor;
    39  import org.codehaus.aspectwerkz.transform.inlining.weaver.SerialVersionUidVisitor;
    40  import org.codehaus.aspectwerkz.transform.inlining.weaver.AddWrapperVisitor;
    41  import org.objectweb.asm.ClassReader;
    42  import org.objectweb.asm.ClassVisitor;
    43  import org.objectweb.asm.ClassWriter;
    44  import org.objectweb.asm.attrs.Attributes;
    45
    46  /***
    47   * A weaving strategy implementing a weaving scheme based on statical compilation, and no reflection.
    48   *
    49   * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
    50   * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
    51   */
    52  public class InliningWeavingStrategy implements WeavingStrategy {
    53
    54      /***
    55       * Performs the weaving of the target class.
    56       *
    57       * @param className
    58       * @param context
    59       */
    60      public void transform(String className, final Context context) {
    61          try {
    62              final byte[] bytecode = context.getInitialBytecode();
    63              final ClassLoader loader = context.getLoader();
    64
    65              ClassInfo classInfo = AsmClassInfo.getClassInfo(bytecode, loader);
    66
    67              // skip Java reflect proxies for which we cannot get the resource as a stream
    68              // which leads to warnings when using annotation matching
    69              // Note: we use an heuristic assuming JDK proxy are classes named "$..."
    70              // to avoid to call getSuperClass everytime
    71              if (classInfo.getName().startsWith("$") && classInfo.getSuperclass().getName().equals("java.lang.reflect.Proxy")) {
    72                  context.setCurrentBytecode(context.getInitialBytecode());
    73                  return;
    74              }
    75
    76              //TODO:FIXME match on (within, null, classInfo) should be equivalent to those ones.
    77              final Set definitions = context.getDefinitions();
    78              final ExpressionContext[] ctxs = new ExpressionContext[]{
    79                  new ExpressionContext(PointcutType.EXECUTION, classInfo, classInfo),
    80                  new ExpressionContext(PointcutType.CALL, null, classInfo),
    81                  new ExpressionContext(PointcutType.GET, null, classInfo),
    82                  new ExpressionContext(PointcutType.SET, null, classInfo),
    83                  new ExpressionContext(PointcutType.HANDLER, null, classInfo),
    84                  new ExpressionContext(PointcutType.STATIC_INITIALIZATION, classInfo, classInfo),
    85                  new ExpressionContext(PointcutType.WITHIN, classInfo, classInfo)
    86              };
    87
    88              if (classFilter(definitions, ctxs, classInfo)) {
    89                  return;
    90              }
    91
    92              // build the ClassInfo from the bytecode to avoid loading it from the loader resource stream later
    93              // to support stub weaving
    94              //AsmClassInfo.getClassInfo(bytecode, loader);
    95
    96              // compute CALL + GET/SET early matching results to avoid registering useless visitors
    97              final boolean filterForCall = classFilterFor(
    98                      definitions, new ExpressionContext[]{
    99                          new ExpressionContext(PointcutType.CALL, null, classInfo),
    100                         new ExpressionContext(PointcutType.WITHIN, classInfo, classInfo)
    101                     }
    102             );//FIXME - within make match all
    103             final boolean filterForGetSet = classFilterFor(
    104                     definitions, new ExpressionContext[]{
    105                         new ExpressionContext(PointcutType.GET, null, classInfo),
    106                         new ExpressionContext(PointcutType.SET, null, classInfo),
    107                         new ExpressionContext(PointcutType.WITHIN, classInfo, classInfo)
    108                     }
    109             );//FIXME - within make match all
    110             final boolean filterForHandler = classFilterFor(
    111                     definitions, new ExpressionContext[]{
    112                         new ExpressionContext(PointcutType.HANDLER, null, classInfo),
    113                         new ExpressionContext(PointcutType.WITHIN, classInfo, classInfo)
    114                     }
    115             );//FIXME - within make match all
    116
    117             // note: for staticinitialization we do an exact match right there
    118             boolean filterForStaticinitialization = !classInfo.hasStaticInitializer()
    119             	||  classFilterFor(definitions, new ExpressionContext[] {
    120     					new ExpressionContext(PointcutType.STATIC_INITIALIZATION,
    121     					                      classInfo.staticInitializer(),
    122     					                      classInfo)
    123     			  		}
    124             		);
    125             if (!filterForStaticinitialization) {
    126                 filterForStaticinitialization = !hasPointcut(definitions, new ExpressionContext(
    127                         PointcutType.STATIC_INITIALIZATION,
    128                         classInfo.staticInitializer(),
    129                         classInfo
    130                 ));
    131             }
    132
    133             // prepare ctor call jp
    134             final ClassReader crLookahead = new ClassReader(bytecode);
    135             TLongObjectHashMap newInvocationsByCallerMemberHash = null;
    136             if (!filterForCall) {
    137                 newInvocationsByCallerMemberHash = new TLongObjectHashMap();
    138                 crLookahead.accept(
    139                         new ConstructorCallVisitor.LookaheadNewDupInvokeSpecialInstructionClassAdapter(
    140                                 newInvocationsByCallerMemberHash
    141                         ),
    142                         true
    143                 );
    144             }
    145
    146             // prepare handler jp, by gathering ALL catch blocks and their exception type
    147             List catchLabels = new ArrayList();
    148             if (!filterForHandler) {
    149                 final ClassReader crLookahead2 = new ClassReader(bytecode);
    150                 final ClassWriter cw2 = AsmHelper.newClassWriter(true);
    151
    152                 HandlerVisitor.LookaheadCatchLabelsClassAdapter lookForCatches =
    153                         new HandlerVisitor.LookaheadCatchLabelsClassAdapter(
    154                                 cw2, loader, classInfo, context, catchLabels
    155                         );
    156                 // we must visit exactly as we will do further on with debug info (that produces extra labels)
    157                 crLookahead2.accept(lookForCatches, Attributes.getDefaultAttributes(), false);
    158             }
    159
    160             // gather wrapper methods to support multi-weaving
    161             // skip annotations visit and debug info by using the lookahead read-only classreader
    162             Set addedMethods = new HashSet();
    163             crLookahead.accept(new AlreadyAddedMethodAdapter(addedMethods), true);
    164
    165             // -- Phase 1 -- type change
    166             final ClassWriter writerPhase1 = AsmHelper.newClassWriter(true);
    167             final ClassReader readerPhase1 = new ClassReader(bytecode);
    168             ClassVisitor reversedChainPhase1 = writerPhase1;
    169             reversedChainPhase1 = new AddMixinMethodsVisitor(reversedChainPhase1, classInfo, context, addedMethods);
    170             reversedChainPhase1 = new AddInterfaceVisitor(reversedChainPhase1, classInfo, context);
    171             readerPhase1.accept(reversedChainPhase1, Attributes.getDefaultAttributes(), false);
    172             final byte[] bytesPhase1 = writerPhase1.toByteArray();
    173
    174             // update the class info
    175             classInfo = AsmClassInfo.newClassInfo(bytesPhase1, loader);
    176
    177             // -- Phase 2 -- advices
    178             final ClassWriter writerPhase2 = AsmHelper.newClassWriter(true);
    179             final ClassReader readerPhase2 = new ClassReader(bytesPhase1);
    180             ClassVisitor reversedChainPhase2 = writerPhase2;
    181             reversedChainPhase2 = new InstanceLevelAspectVisitor(reversedChainPhase2, classInfo, context);
    182             reversedChainPhase2 = new MethodExecutionVisitor(reversedChainPhase2, classInfo, context, addedMethods);
    183             reversedChainPhase2 = new ConstructorBodyVisitor(reversedChainPhase2, classInfo, context, addedMethods);
    184             if(!filterForStaticinitialization) {
    185             	reversedChainPhase2 = new StaticInitializationVisitor(reversedChainPhase2, context, addedMethods);
    186             }
    187             reversedChainPhase2 = new HandlerVisitor(reversedChainPhase2, context, catchLabels);
    188             if (!filterForCall) {
    189                 reversedChainPhase2 = new MethodCallVisitor(reversedChainPhase2, loader, classInfo, context);
    190                 reversedChainPhase2 = new ConstructorCallVisitor(
    191                         reversedChainPhase2, loader, classInfo, context, newInvocationsByCallerMemberHash
    192                 );
    193             }
    194             if (!filterForGetSet) {
    195                 reversedChainPhase2 = new FieldSetFieldGetVisitor(reversedChainPhase2, loader, classInfo, context);
    196             }
    197             reversedChainPhase2 = new LabelToLineNumberVisitor(reversedChainPhase2, context);
    198             readerPhase2.accept(reversedChainPhase2, Attributes.getDefaultAttributes(), false);
    199             final byte[] bytesPhase2 = writerPhase2.toByteArray();
    200
    201             context.setCurrentBytecode(bytesPhase2);
    202
    203             // -- Phase 3 -- serialUID and JoinPoint initialization
    204             if (context.isAdvised()) {
    205                 final ClassWriter writerPhase3 = AsmHelper.newClassWriter(true);
    206                 ClassReader readerPhase3 = new ClassReader(bytesPhase2);
    207                 ClassVisitor reversedChainPhase3 = writerPhase3;
    208                 reversedChainPhase3 = new SerialVersionUidVisitor.Add(reversedChainPhase3, context, classInfo);
    209                 reversedChainPhase3 = new AddWrapperVisitor(reversedChainPhase3, context, addedMethods);
    210                 reversedChainPhase3 = new JoinPointInitVisitor(reversedChainPhase3, context);
    211                 readerPhase3.accept(reversedChainPhase3, Attributes.getDefaultAttributes(), false);
    212                 final byte[] bytesPhase3 = writerPhase3.toByteArray();
    213
    214                 context.setCurrentBytecode(bytesPhase3);
    215             }
    216
    217             // TODO: INNER CLASS OR NOT?
    218             // loop over emitted jp and flag them as inner classes
    219 //            for (Iterator iterator = ((ContextImpl) context).getEmittedInlinedJoinPoint().iterator(); iterator.hasNext();) {
    220 //                String joinPointClassName = ((ContextImpl.EmittedJoinPoint) iterator.next()).joinPointClassName;
    221 //                int innerIndex = joinPointClassName.lastIndexOf('$');
    222 //                cw.visitInnerClass(joinPointClassName,
    223 //                        joinPointClassName.substring(0, innerIndex),
    224 //                        joinPointClassName.substring(innerIndex + 1, joinPointClassName.length()),
    225 //                        Constants.ACC_PUBLIC + Constants.ACC_STATIC);
    226 //            }
    227
    228 //            // resolve line numbers - debug only
    229 //            List ejp = ((ContextImpl)context).getEmittedJoinPoints();
    230 //            for (Iterator iterator = ejp.iterator(); iterator.hasNext();) {
    231 //                EmittedJoinPoint emittedJoinPoint = (EmittedJoinPoint) iterator.next();
    232 //                emittedJoinPoint.resolveLineNumber(context);
    233 //                System.out.println(emittedJoinPoint.toString());
    234 //            }
    235
    236             // NOTE: remove when in release time or in debugging trouble (;-) - Alex)
    237             // FAKE multiweaving - which is a requirement
    238             //            Object multi = context.getMetaData("FAKE");
    239             //            if (multi == null) {
    240             //                context.addMetaData("FAKE", "FAKE");
    241             //                transform(className, context);
    242             //            }
    243
    244         } catch (Throwable t) {
    245             t.printStackTrace();
    246             throw new WrappedRuntimeException(t);
    247         }
    248     }
    249
    250     /***
    251      * Creates a new transformation context.
    252      *
    253      * @param name
    254      * @param bytecode
    255      * @param loader
    256      * @return
    257      */
    258     public Context newContext(final String name, final byte[] bytecode, final ClassLoader loader) {
    259         return new ContextImpl(name, bytecode, loader);
    260     }
    261
    262     /***
    263      * Filters out the classes that are not eligible for transformation.
    264      *
    265      * @param definitions the definitions
    266      * @param ctxs        an array with the contexts
    267      * @param classInfo   the class to filter
    268      * @return boolean true if the class should be filtered out
    269      */
    270     private static boolean classFilter(final Set definitions,
    271                                        final ExpressionContext[] ctxs,
    272                                        final ClassInfo classInfo) {
    273         if (classInfo.isInterface()) {
    274             return true;
    275         }
    276         for (Iterator defs = definitions.iterator(); defs.hasNext();) {
    277             if (classFilter((SystemDefinition) defs.next(), ctxs, classInfo)) {
    278                 continue;
    279             } else {
    280                 return false;
    281             }
    282         }
    283         return true;
    284     }
    285
    286     /***
    287      * Filters out the classes that are not eligible for transformation.
    288      *
    289      * @param definition the definition
    290      * @param ctxs       an array with the contexts
    291      * @param classInfo  the class to filter
    292      * @return boolean true if the class should be filtered out
    293      * @TODO: when a class had execution pointcut that were removed it must be unweaved, thus not filtered out How to
    294      * handle that? cache lookup? or custom class level attribute ?
    295      */
    296     private static boolean classFilter(final SystemDefinition definition,
    297                                        final ExpressionContext[] ctxs,
    298                                        final ClassInfo classInfo) {
    299         if (classInfo.isInterface()) {
    300             return true;
    301         }
    302         String className = classInfo.getName();
    303         if (definition.inExcludePackage(className)) {
    304             return true;
    305         }
    306         if (!definition.inIncludePackage(className)) {
    307             return true;
    308         }
    309         if (definition.isAdvised(ctxs)) {
    310             return false;
    311         }
    312         if (definition.hasMixin(ctxs)) {
    313             return false;
    314         }
    315         if (definition.hasIntroducedInterface(ctxs)) {
    316             return false;
    317         }
    318         if (definition.inPreparePackage(className)) {
    319             return false;
    320         }
    321         return true;
    322     }
    323
    324     private static boolean classFilterFor(final Set definitions,
    325                                           final ExpressionContext[] ctxs) {
    326         for (Iterator defs = definitions.iterator(); defs.hasNext();) {
    327             if (classFilterFor((SystemDefinition) defs.next(), ctxs)) {
    328                 continue;
    329             } else {
    330                 return false;
    331             }
    332         }
    333         return true;
    334     }
    335
    336     private static boolean classFilterFor(final SystemDefinition definition,
    337                                           final ExpressionContext[] ctxs) {
    338         if (definition.isAdvised(ctxs)) {
    339             return false;
    340         }
    341         return true;
    342     }
    343
    344     private static boolean hasPointcut(final Set definitions,
    345                                        final ExpressionContext ctx) {
    346         for (Iterator defs = definitions.iterator(); defs.hasNext();) {
    347             if (hasPointcut((SystemDefinition) defs.next(), ctx)) {
    348                 return true;
    349             } else {
    350                 continue;
    351             }
    352         }
    353         return false;
    354     }
    355
    356     private static boolean hasPointcut(final SystemDefinition definition,
    357                                        final ExpressionContext ctx) {
    358         if (definition.hasPointcut(ctx)) {
    359             return true;
    360         }
    361         return false;
    362     }
    363 }