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
241 NewInvocationStruct newInvocationStruct = (NewInvocationStruct) m_newInvocations.get(
242 m_newInvocationIndex
243 );
244 if (newInvocationStruct == null) {
245 super.visitTypeInsn(opcode, desc);
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);
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
266 ExpressionContext ctx = new ExpressionContext(
267 PointcutType.CALL, calleeConstructorInfo, m_callerMemberInfo
268 );
269 if (constructorFilter(m_ctx.getDefinitions(), ctx, calleeConstructorInfo)) {
270
271 m_newInvocationStructStack.push(null);
272 super.visitTypeInsn(opcode, desc);
273 } else {
274
275 newInvocationStruct.constructorInfo = calleeConstructorInfo;
276 newInvocationStruct.joinPointHash = joinPointHash;
277 m_newInvocationStructStack.push(newInvocationStruct);
278
279 m_skipNextDup = true;
280
281 }
282 } else {
283
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
296 ;
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
330 if (m_newInvocationStructStack.isEmpty()) {
331
332 super.visitMethodInsn(opcode, calleeClassName, calleeConstructorName, calleeConstructorDesc);
333 return;
334 }
335
336 NewInvocationStruct struct = (NewInvocationStruct) m_newInvocationStructStack.pop();
337 if (struct == null) {
338
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
353
354 if (Modifier.isStatic(m_callerMemberInfo.getModifiers())) {
355 visitInsn(ACONST_NULL);
356 } else {
357 visitVarInsn(ALOAD, 0);
358 }
359
360
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
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
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 ;
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
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
497 super.visitMethodInsn(opcode, calleeClassName, calleeMethodName, calleeMethodDesc);
498
499 if (INIT_METHOD_NAME.equals(calleeMethodName) && opcode == INVOKESPECIAL) {
500 if (!m_isObjectInitialized) {
501
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
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 }