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