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 }