001: /**************************************************************************************
002: * Copyright (c) Jonas BonŽr, Alexandre Vasseur. All rights reserved. *
003: * http://aspectwerkz.codehaus.org *
004: * ---------------------------------------------------------------------------------- *
005: * The software in this package is published under the terms of the LGPL license *
006: * a copy of which has been included with this distribution in the license.txt file. *
007: **************************************************************************************/package org.codehaus.aspectwerkz.transform.inlining.weaver;
008:
009: import org.objectweb.asm.ClassAdapter;
010: import org.objectweb.asm.ClassVisitor;
011: import org.objectweb.asm.CodeVisitor;
012: import org.objectweb.asm.Attribute;
013: import org.objectweb.asm.Constants;
014: import org.objectweb.asm.CodeAdapter;
015: import org.objectweb.asm.Label;
016: import org.codehaus.aspectwerkz.definition.SystemDefinition;
017: import org.codehaus.aspectwerkz.expression.ExpressionContext;
018: import org.codehaus.aspectwerkz.expression.PointcutType;
019: import org.codehaus.aspectwerkz.joinpoint.management.JoinPointType;
020: import org.codehaus.aspectwerkz.reflect.ClassInfo;
021: import org.codehaus.aspectwerkz.reflect.ConstructorInfo;
022: import org.codehaus.aspectwerkz.reflect.MemberInfo;
023: import org.codehaus.aspectwerkz.reflect.impl.asm.AsmClassInfo;
024: import org.codehaus.aspectwerkz.transform.Context;
025: import org.codehaus.aspectwerkz.transform.TransformationUtil;
026: import org.codehaus.aspectwerkz.transform.TransformationConstants;
027: import org.codehaus.aspectwerkz.transform.inlining.compiler.AbstractJoinPointCompiler;
028: import org.codehaus.aspectwerkz.transform.inlining.ContextImpl;
029: import org.codehaus.aspectwerkz.transform.inlining.AsmHelper;
030: import org.codehaus.aspectwerkz.transform.inlining.EmittedJoinPoint;
031: import org.codehaus.aspectwerkz.annotation.instrumentation.asm.AsmAnnotationHelper;
032:
033: import java.lang.reflect.Modifier;
034: import java.util.Iterator;
035: import java.util.List;
036: import java.util.Stack;
037: import java.util.Set;
038:
039: import gnu.trove.TLongObjectHashMap;
040: import gnu.trove.TIntObjectHashMap;
041:
042: /**
043: * Instruments ctor CALL join points by replacing INVOKEXXX instructions with invocations of the compiled join point.
044: * <br/>
045: * It calls the JPClass.invoke static method. The signature of the invoke method is:
046: * <pre>
047: * invoke(args.., caller) - note: no callee as arg0
048: * </pre>
049: * (The reason why is that it simplifies call pointcut stack management)
050: *
051: * <p/>
052: * Note: The Eclipse compiler is generating "catch(exception) NEW DUP_X1 SWAP getMessage newError(..)"
053: * hence NEW DUP_X1 is a valid sequence as well, and DUP_X1 is replaced by DUP to preserved the SWAP.
054: * Other more complex schemes (DUP_X2) are not implemented (no real test so far)
055: *
056: * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
057: */
058: public class ConstructorCallVisitor extends ClassAdapter implements
059: TransformationConstants {
060:
061: private final static TIntObjectHashMap EMPTY_INTHASHMAP = new TIntObjectHashMap(
062: 0);
063:
064: private final ContextImpl m_ctx;
065: private final ClassLoader m_loader;
066: private final ClassInfo m_callerClassInfo;
067:
068: /**
069: * Map of NEW instructions.
070: * The key is the method (withincode) hash
071: * The value is a TLongObjectHashMap whose key is index of NEW instructions and value instance of NewInvocationStruct
072: */
073: private final TLongObjectHashMap m_newInvocationsByCallerMemberHash;
074:
075: private Label m_lastLabelForLineNumber = EmittedJoinPoint.NO_LINE_NUMBER;
076:
077: /**
078: * Creates a new instance.
079: *
080: * @param cv
081: * @param loader
082: * @param classInfo
083: * @param ctx
084: */
085: public ConstructorCallVisitor(final ClassVisitor cv,
086: final ClassLoader loader, final ClassInfo classInfo,
087: final Context ctx,
088: final TLongObjectHashMap newInvocationsByCallerMemberHash) {
089: super (cv);
090: m_loader = loader;
091: m_callerClassInfo = classInfo;
092: m_ctx = (ContextImpl) ctx;
093: m_newInvocationsByCallerMemberHash = newInvocationsByCallerMemberHash;
094: }
095:
096: /**
097: * Visits the caller methods.
098: *
099: * @param access
100: * @param name
101: * @param desc
102: * @param exceptions
103: * @param attrs
104: * @return
105: */
106: public CodeVisitor visitMethod(final int access, final String name,
107: final String desc, final String[] exceptions,
108: final Attribute attrs) {
109:
110: if (name.startsWith(WRAPPER_METHOD_PREFIX)
111: || Modifier.isNative(access)
112: || Modifier.isAbstract(access)) {
113: return super .visitMethod(access, name, desc, exceptions,
114: attrs);
115: }
116:
117: CodeVisitor mv = cv.visitMethod(access, name, desc, exceptions,
118: attrs);
119: return mv == null ? null
120: : new ReplaceNewInstructionCodeAdapter(
121: mv,
122: m_loader,
123: m_callerClassInfo,
124: m_ctx.getClassName(),
125: name,
126: desc,
127: (TIntObjectHashMap) m_newInvocationsByCallerMemberHash
128: .get(getMemberHash(name, desc)));
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
197: : EMPTY_INTHASHMAP;
198:
199: if (CLINIT_METHOD_NAME.equals(m_callerMethodName)) {
200: m_callerMemberInfo = m_callerClassInfo
201: .staticInitializer();
202: } else if (INIT_METHOD_NAME.equals(m_callerMethodName)) {
203: final int hash = AsmHelper
204: .calculateConstructorHash(m_callerMethodDesc);
205: m_callerMemberInfo = m_callerClassInfo
206: .getConstructor(hash);
207: } else {
208: final int hash = AsmHelper.calculateMethodHash(
209: m_callerMethodName, m_callerMethodDesc);
210: m_callerMemberInfo = m_callerClassInfo.getMethod(hash);
211: }
212: if (m_callerMemberInfo == null) {
213: System.err
214: .println("AW::WARNING "
215: + "metadata structure could not be build for method ["
216: + m_callerClassInfo.getName().replace(
217: '/', '.') + '.'
218: + m_callerMethodName + ':'
219: + m_callerMethodDesc + ']');
220: }
221: }
222:
223: /**
224: * Label
225: *
226: * @param label
227: */
228: public void visitLabel(Label label) {
229: m_lastLabelForLineNumber = label;
230: super .visitLabel(label);
231: }
232:
233: /**
234: * Removes the NEW when we know that the corresponding INVOKE SPECIAL <init> is advised.
235: *
236: * @param opcode
237: * @param desc
238: */
239: public void visitTypeInsn(int opcode, String desc) {
240: if (m_callerMemberInfo == null) {
241: return;
242: }
243:
244: if (opcode == NEW) {
245: m_newInvocationIndex++;
246: // build the callee ConstructorInfo and check for a match
247: NewInvocationStruct newInvocationStruct = (NewInvocationStruct) m_newInvocations
248: .get(m_newInvocationIndex);
249: if (newInvocationStruct == null) {
250: super .visitTypeInsn(opcode, desc);//we failed
251: return;
252: }
253: String calleeClassName = newInvocationStruct.className;
254: String calleeMethodName = INIT_METHOD_NAME;
255: String calleeMethodDesc = newInvocationStruct.ctorDesc;
256: int joinPointHash = AsmHelper.calculateMethodHash(
257: calleeMethodName, calleeMethodDesc);
258: ClassInfo classInfo = AsmClassInfo.getClassInfo(
259: calleeClassName, m_loader);
260: ConstructorInfo calleeConstructorInfo = classInfo
261: .getConstructor(joinPointHash);
262: if (calleeConstructorInfo == null) {
263: super .visitTypeInsn(opcode, desc);//we failed
264: System.err
265: .println("AW::WARNING "
266: + "metadata structure could not be build for method ["
267: + classInfo.getName().replace('/',
268: '.') + '.'
269: + calleeMethodName + ':'
270: + calleeMethodDesc + ']');
271: return;
272: }
273:
274: // do we have a match - if so, skip the NEW and the DUP
275: ExpressionContext ctx = new ExpressionContext(
276: PointcutType.CALL, calleeConstructorInfo,
277: m_callerMemberInfo);
278: if (constructorFilter(m_ctx.getDefinitions(), ctx,
279: calleeConstructorInfo)) {
280: // push NULL as a struct (means no match)
281: m_newInvocationStructStack.push(null);
282: super .visitTypeInsn(opcode, desc);
283: } else {
284: // keep track of the ConstructorInfo so that we don't compute it again in visitMethodInsn <init>
285: newInvocationStruct.constructorInfo = calleeConstructorInfo;
286: newInvocationStruct.joinPointHash = joinPointHash;
287: m_newInvocationStructStack
288: .push(newInvocationStruct);
289: // skip NEW instr and flag to skip next DUP
290: m_skipNextDup = true;
291: //System.out.println("RECORD " + calleeClassName + calleeMethodDesc);
292: }
293: } else {
294: // is not a NEW instr
295: super .visitTypeInsn(opcode, desc);
296: }
297: }
298:
299: /**
300: * Remove the DUP instruction if we know that those were for a NEW ... INVOKESPECIAL that match.
301: *
302: * @param opcode
303: */
304: public void visitInsn(int opcode) {
305: if ((opcode == DUP || opcode == DUP_X1) && m_skipNextDup) {
306: //System.out.println("SKIP dup");
307: ;// skip the DUP
308: if (opcode == DUP_X1)
309: super .visitInsn(DUP);
310: } else {
311: super .visitInsn(opcode);
312: }
313: m_skipNextDup = false;
314: }
315:
316: /**
317: * Visits INVOKESPECIAL <init> instructions and replace them with a call to the join point when matched.
318: *
319: * @param opcode
320: * @param calleeClassName
321: * @param calleeConstructorName
322: * @param calleeConstructorDesc
323: */
324: public void visitMethodInsn(final int opcode,
325: final String calleeClassName,
326: final String calleeConstructorName,
327: final String calleeConstructorDesc) {
328:
329: if (m_callerMemberInfo == null) {
330: super .visitMethodInsn(opcode, calleeClassName,
331: calleeConstructorName, calleeConstructorDesc);
332: return;
333: }
334:
335: if (!INIT_METHOD_NAME.equals(calleeConstructorName)
336: || calleeClassName
337: .endsWith(AbstractJoinPointCompiler.JOIN_POINT_CLASS_SUFFIX)) {
338: super .visitMethodInsn(opcode, calleeClassName,
339: calleeConstructorName, calleeConstructorDesc);
340: return;
341: }
342:
343: // get the info from the invocation stack since all the matching has already been done
344: if (m_newInvocationStructStack.isEmpty()) {
345: // nothing to weave
346: super .visitMethodInsn(opcode, calleeClassName,
347: calleeConstructorName, calleeConstructorDesc);
348: return;
349: }
350:
351: NewInvocationStruct struct = (NewInvocationStruct) m_newInvocationStructStack
352: .pop();
353: if (struct == null) {
354: // not matched
355: super .visitMethodInsn(opcode, calleeClassName,
356: calleeConstructorName, calleeConstructorDesc);
357: } else {
358: m_ctx.markAsAdvised();
359:
360: String joinPointClassName = TransformationUtil
361: .getJoinPointClassName(m_callerClassName,
362: m_callerMethodName, m_callerMethodDesc,
363: calleeClassName,
364: JoinPointType.CONSTRUCTOR_CALL_INT,
365: struct.joinPointHash);
366:
367: // load the caller instance (this), or null if in a static context
368: // note that callee instance [mandatory since ctor] and args are already on the stack
369: if (Modifier
370: .isStatic(m_callerMemberInfo.getModifiers())) {
371: visitInsn(ACONST_NULL);
372: } else {
373: visitVarInsn(ALOAD, 0);
374: }
375:
376: // add the call to the join point
377: super
378: .visitMethodInsn(
379: INVOKESTATIC,
380: joinPointClassName,
381: INVOKE_METHOD_NAME,
382: TransformationUtil
383: .getInvokeSignatureForConstructorCallJoinPoints(
384: calleeConstructorDesc,
385: m_callerClassName,
386: calleeClassName));
387:
388: // emit the joinpoint
389: m_ctx.addEmittedJoinPoint(new EmittedJoinPoint(
390: JoinPointType.CONSTRUCTOR_CALL_INT,
391: m_callerClassName, m_callerMethodName,
392: m_callerMethodDesc, m_callerMemberInfo
393: .getModifiers(), calleeClassName,
394: calleeConstructorName, calleeConstructorDesc,
395: struct.constructorInfo.getModifiers(),
396: struct.joinPointHash, joinPointClassName,
397: m_lastLabelForLineNumber));
398: }
399: }
400:
401: /**
402: * Filters out the ctor that are not eligible for transformation.
403: *
404: * @param definitions
405: * @param ctx
406: * @param calleeConstructorInfo
407: * @return boolean true if the method should be filtered out
408: */
409: public boolean constructorFilter(final Set definitions,
410: final ExpressionContext ctx,
411: final ConstructorInfo calleeConstructorInfo) {
412: for (Iterator it = definitions.iterator(); it.hasNext();) {
413: if (((SystemDefinition) it.next()).hasPointcut(ctx)) {
414: return false;
415: } else {
416: continue;
417: }
418: }
419: return true;
420: }
421: }
422:
423: private static int getMemberHash(String name, String desc) {
424: int hash = 29;
425: hash = (29 * hash) + name.hashCode();
426: return (29 * hash) + desc.hashCode();
427: }
428:
429: /**
430: * Lookahead index of NEW instruction for NEW + DUP + INVOKESPECIAL instructions
431: * Remember the NEW instruction index
432: * <p/>
433: * Special case when withincode ctor of called ctor:
434: * <pre>public Foo() { super(new Foo()); }</pre>
435: * In such a case, it is not possible to intercept the call to new Foo() since this cannot be
436: * referenced as long as this(..) or super(..) has not been called.
437: *
438: * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
439: */
440: public static class LookaheadNewDupInvokeSpecialInstructionClassAdapter
441: extends AsmAnnotationHelper.NullClassAdapter {
442:
443: private String m_callerMemberName;
444:
445: // list of new invocations by caller member hash
446: public TLongObjectHashMap m_newInvocationsByCallerMemberHash;
447:
448: public LookaheadNewDupInvokeSpecialInstructionClassAdapter(
449: TLongObjectHashMap newInvocations) {
450: m_newInvocationsByCallerMemberHash = newInvocations;
451: }
452:
453: public CodeVisitor visitMethod(final int access,
454: final String name, final String desc,
455: final String[] exceptions, final Attribute attrs) {
456: if (name.startsWith(WRAPPER_METHOD_PREFIX)
457: || Modifier.isNative(access)
458: || Modifier.isAbstract(access)) {
459: ;//ignore
460: }
461:
462: m_callerMemberName = name;
463:
464: TIntObjectHashMap newInvocations = new TIntObjectHashMap(5);
465: m_newInvocationsByCallerMemberHash.put(getMemberHash(name,
466: desc), newInvocations);
467: return new LookaheadNewDupInvokeSpecialInstructionCodeAdapter(
468: super .visitMethod(access, name, desc, exceptions,
469: attrs), newInvocations, m_callerMemberName);
470: }
471: }
472:
473: public static class LookaheadNewDupInvokeSpecialInstructionCodeAdapter
474: extends AfterObjectInitializationCodeAdapter {
475:
476: private TIntObjectHashMap m_newInvocations;
477:
478: private Stack m_newIndexStack = new Stack();
479: private int m_newIndex = -1;
480:
481: /**
482: * Creates a new instance.
483: */
484: public LookaheadNewDupInvokeSpecialInstructionCodeAdapter(
485: CodeVisitor cv, TIntObjectHashMap newInvocations,
486: final String callerMemberName) {
487: super (cv, callerMemberName);
488: m_newInvocations = newInvocations;
489: }
490:
491: public void visitTypeInsn(int opcode, String desc) {
492: // make sure to call super first to compute post object initialization flag
493: super .visitTypeInsn(opcode, desc);
494: if (opcode == NEW) {
495: m_newIndex++;
496: m_newIndexStack.push(new Integer(m_newIndex));
497: }
498: }
499:
500: public void visitMethodInsn(final int opcode,
501: final String calleeClassName,
502: final String calleeMethodName,
503: final String calleeMethodDesc) {
504: // make sure to call super first to compute post object initialization flag
505: super .visitMethodInsn(opcode, calleeClassName,
506: calleeMethodName, calleeMethodDesc);
507:
508: if (INIT_METHOD_NAME.equals(calleeMethodName)
509: && opcode == INVOKESPECIAL) {
510: if (!m_isObjectInitialized) {
511: // skip - remove the NEW index from the stack
512: if (!m_newIndexStack.isEmpty()) {
513: m_newIndexStack.pop();
514: }
515: } else {
516: if (!m_newIndexStack.isEmpty()) {
517: int index = ((Integer) m_newIndexStack.pop())
518: .intValue();
519: NewInvocationStruct newInvocationStruct = new NewInvocationStruct();
520: newInvocationStruct.className = calleeClassName;
521: newInvocationStruct.ctorDesc = calleeMethodDesc;
522: // constructorInfo and matching will be done at weave time and not at lookahead time
523: m_newInvocations
524: .put(index, newInvocationStruct);
525: }
526: }
527: }
528: }
529: }
530:
531: private static class NewInvocationStruct {
532: public String className;
533: public String ctorDesc;
534: public ConstructorInfo constructorInfo = null;
535: public int joinPointHash = -1;
536: }
537:
538: }
|