001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
003: * notice. All rights reserved.
004: */
005: package com.tc.object.bytecode.hook.impl;
006:
007: import com.tc.asm.ClassReader;
008: import com.tc.asm.ClassVisitor;
009: import com.tc.asm.ClassWriter;
010: import com.tc.asm.commons.EmptyVisitor;
011: import com.tc.aspectwerkz.definition.SystemDefinition;
012: import com.tc.aspectwerkz.definition.deployer.StandardAspectModuleDeployer;
013: import com.tc.aspectwerkz.exception.WrappedRuntimeException;
014: import com.tc.aspectwerkz.expression.ExpressionContext;
015: import com.tc.aspectwerkz.expression.PointcutType;
016: import com.tc.aspectwerkz.reflect.ClassInfo;
017: import com.tc.aspectwerkz.reflect.ClassInfoHelper;
018: import com.tc.aspectwerkz.reflect.impl.asm.AsmClassInfo;
019: import com.tc.aspectwerkz.transform.InstrumentationContext;
020: import com.tc.aspectwerkz.transform.WeavingStrategy;
021: import com.tc.aspectwerkz.transform.inlining.weaver.AddInterfaceVisitor;
022: import com.tc.aspectwerkz.transform.inlining.weaver.AddMixinMethodsVisitor;
023: import com.tc.aspectwerkz.transform.inlining.weaver.AddWrapperVisitor;
024: import com.tc.aspectwerkz.transform.inlining.weaver.AlreadyAddedMethodAdapter;
025: import com.tc.aspectwerkz.transform.inlining.weaver.ConstructorBodyVisitor;
026: import com.tc.aspectwerkz.transform.inlining.weaver.ConstructorCallVisitor;
027: import com.tc.aspectwerkz.transform.inlining.weaver.FieldSetFieldGetVisitor;
028: import com.tc.aspectwerkz.transform.inlining.weaver.HandlerVisitor;
029: import com.tc.aspectwerkz.transform.inlining.weaver.InstanceLevelAspectVisitor;
030: import com.tc.aspectwerkz.transform.inlining.weaver.JoinPointInitVisitor;
031: import com.tc.aspectwerkz.transform.inlining.weaver.LabelToLineNumberVisitor;
032: import com.tc.aspectwerkz.transform.inlining.weaver.MethodCallVisitor;
033: import com.tc.aspectwerkz.transform.inlining.weaver.MethodExecutionVisitor;
034: import com.tc.aspectwerkz.transform.inlining.weaver.StaticInitializationVisitor;
035: import com.tc.exception.TCLogicalSubclassNotPortableException;
036: import com.tc.logging.CustomerLogging;
037: import com.tc.logging.TCLogger;
038: import com.tc.object.bytecode.ByteCodeUtil;
039: import com.tc.object.bytecode.ClassAdapterFactory;
040: import com.tc.object.bytecode.RenameClassesAdapter;
041: import com.tc.object.bytecode.SafeSerialVersionUIDAdder;
042: import com.tc.object.config.ClassReplacementMapping;
043: import com.tc.object.config.DSOClientConfigHelper;
044: import com.tc.object.logging.InstrumentationLogger;
045: import com.tc.object.logging.InstrumentationLoggerImpl;
046: import com.tc.util.AdaptedClassDumper;
047: import com.tc.util.InitialClassDumper;
048:
049: import java.io.IOException;
050: import java.io.InputStream;
051: import java.net.URL;
052: import java.text.MessageFormat;
053: import java.util.ArrayList;
054: import java.util.HashMap;
055: import java.util.HashSet;
056: import java.util.Iterator;
057: import java.util.List;
058: import java.util.Map;
059: import java.util.Set;
060:
061: /**
062: * A weaving strategy implementing a weaving scheme based on statical compilation, and no reflection.
063: *
064: * @author <a href="mailto:jboner@codehaus.org">Jonas Bonér </a>
065: * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
066: */
067: public class DefaultWeavingStrategy implements WeavingStrategy {
068:
069: static {
070: // Load the InitialClassDumper class in the same class loader as the
071: // current class to ensure that it will not be transformed while
072: // being loaded by a child class loader as this would result in a
073: // ClassCircularityError exception
074: InitialClassDumper dummy = InitialClassDumper.INSTANCE;
075: }
076:
077: private static final TCLogger consoleLogger = CustomerLogging
078: .getConsoleLogger();
079:
080: private final DSOClientConfigHelper m_configHelper;
081: private final InstrumentationLogger m_logger;
082: private final InstrumentationLogger m_instrumentationLogger;
083:
084: public DefaultWeavingStrategy(
085: final DSOClientConfigHelper configHelper,
086: InstrumentationLogger instrumentationLogger) {
087: m_configHelper = configHelper;
088: m_instrumentationLogger = instrumentationLogger;
089: m_logger = new InstrumentationLoggerImpl(m_configHelper
090: .getInstrumentationLoggingOptions());
091:
092: // deploy all system aspect modules
093: StandardAspectModuleDeployer.deploy(
094: getClass().getClassLoader(),
095: StandardAspectModuleDeployer.ASPECT_MODULES);
096: }
097:
098: /**
099: * Performs the weaving of the target class.
100: *
101: * @param className
102: * @param context
103: */
104: public void transform(String className,
105: final InstrumentationContext context) {
106: try {
107: final byte[] bytecode = context.getInitialBytecode();
108: InitialClassDumper.INSTANCE.write(className, bytecode);
109:
110: final ClassLoader loader = context.getLoader();
111:
112: Map aspectModules = m_configHelper.getAspectModules();
113: for (Iterator it = aspectModules.entrySet().iterator(); it
114: .hasNext();) {
115: Map.Entry e = (Map.Entry) it.next();
116: if (className.startsWith((String) e.getKey())) {
117: List modules = (List) e.getValue();
118: for (Iterator it2 = modules.iterator(); it2
119: .hasNext();) {
120: StandardAspectModuleDeployer.deploy(loader,
121: (String) it2.next());
122: }
123: }
124: }
125:
126: ClassInfo classInfo = AsmClassInfo.getClassInfo(className,
127: bytecode, loader);
128:
129: // skip Java reflect proxies for which we cannot get the resource as a stream
130: // which leads to warnings when using annotation matching
131: // Note: we use an heuristic assuming JDK proxy are classes named "$..."
132: // to avoid to call getSuperClass everytime
133: if (classInfo.getName().startsWith("$")
134: && classInfo.getSuperclass().getName().equals(
135: "java.lang.reflect.Proxy")) {
136: context
137: .setCurrentBytecode(context
138: .getInitialBytecode());
139: return;
140: }
141:
142: // filtering out all proxy classes that have been transformed by DSO already
143: if (context.isProxy() && isInstrumentedByDSO(classInfo)) {
144: context
145: .setCurrentBytecode(context
146: .getInitialBytecode());
147: return;
148: }
149:
150: final boolean isDsoAdaptable = m_configHelper
151: .shouldBeAdapted(classInfo);
152: final boolean hasCustomAdapter = m_configHelper
153: .hasCustomAdapter(classInfo);
154:
155: // TODO match on (within, null, classInfo) should be equivalent to those ones.
156: final Set definitions = context.getDefinitions();
157: final ExpressionContext[] ctxs = new ExpressionContext[] {
158: new ExpressionContext(PointcutType.EXECUTION,
159: classInfo, classInfo),
160: new ExpressionContext(PointcutType.CALL, null,
161: classInfo),
162: new ExpressionContext(PointcutType.GET, null,
163: classInfo),
164: new ExpressionContext(PointcutType.SET, null,
165: classInfo),
166: new ExpressionContext(PointcutType.HANDLER, null,
167: classInfo),
168: new ExpressionContext(
169: PointcutType.STATIC_INITIALIZATION,
170: classInfo, classInfo),
171: new ExpressionContext(PointcutType.WITHIN,
172: classInfo, classInfo) };
173:
174: // has AW aspects?
175: final boolean isAdvisable = !classFilter(definitions, ctxs,
176: classInfo);
177:
178: if (!isAdvisable && !isDsoAdaptable && !hasCustomAdapter) {
179: context
180: .setCurrentBytecode(context
181: .getInitialBytecode());
182: return;
183: }
184:
185: if (m_instrumentationLogger.classInclusion()) {
186: m_instrumentationLogger.classIncluded(className);
187: }
188:
189: // handle replacement classes
190: if (isDsoAdaptable || hasCustomAdapter) {
191: ClassReplacementMapping mapping = m_configHelper
192: .getClassReplacementMapping();
193: String replacementClassName = mapping
194: .getReplacementClassName(className);
195:
196: // check if there's a replacement class
197: if (replacementClassName != null
198: && !replacementClassName.equals(className)) {
199: // obtain the resource of the replacement class either from a module bundle, or from the
200: // active classloader
201: URL replacementResource = mapping
202: .getReplacementResource(
203: replacementClassName, loader);
204: if (replacementResource == null) {
205: throw new ClassNotFoundException(
206: "No resource found for class: "
207: + replacementClassName);
208: }
209:
210: // obtain the bytes of the replacement class
211: InputStream is = replacementResource.openStream();
212: try {
213: byte[] replacementBytes = ByteCodeUtil
214: .getBytesForInputstream(is);
215:
216: // perform the rename transformation so that it can be used instead of the original class
217: ClassReader cr = new ClassReader(
218: replacementBytes);
219: ClassWriter cw = new ClassWriter(cr,
220: ClassWriter.COMPUTE_MAXS);
221: ClassVisitor cv = new RenameClassesAdapter(cw,
222: mapping);
223: cr.accept(cv, ClassReader.SKIP_FRAMES);
224:
225: context.setCurrentBytecode(cw.toByteArray());
226:
227: // update the classInfo
228: classInfo = AsmClassInfo.newClassInfo(context
229: .getCurrentBytecode(), loader);
230:
231: } catch (IOException e) {
232: throw new ClassNotFoundException(
233: "Error reading bytes for "
234: + replacementResource, e);
235: } finally {
236: is.close();
237: }
238: }
239: }
240:
241: // ------------------------------------------------
242: // -- Phase AW -- weave in aspects
243: if (isAdvisable) {
244: // compute CALL + GET/SET early matching results to avoid registering useless visitors
245: final boolean filterForCall = classFilterFor(
246: definitions, new ExpressionContext[] {
247: new ExpressionContext(
248: PointcutType.CALL, null,
249: classInfo),
250: new ExpressionContext(
251: PointcutType.WITHIN, classInfo,
252: classInfo) });// FIXME - within make match
253: // all
254: final boolean filterForGetSet = classFilterFor(
255: definitions, new ExpressionContext[] {
256: new ExpressionContext(PointcutType.GET,
257: null, classInfo),
258: new ExpressionContext(PointcutType.SET,
259: null, classInfo),
260: new ExpressionContext(
261: PointcutType.WITHIN, classInfo,
262: classInfo) });// FIXME - within make match
263: // all
264: final boolean filterForHandler = classFilterFor(
265: definitions, new ExpressionContext[] {
266: new ExpressionContext(
267: PointcutType.HANDLER, null,
268: classInfo),
269: new ExpressionContext(
270: PointcutType.WITHIN, classInfo,
271: classInfo) });// FIXME - within make match
272: // all
273:
274: // note: for staticinitialization we do an exact match right there
275: boolean filterForStaticinitialization = !classInfo
276: .hasStaticInitializer()
277: || classFilterFor(
278: definitions,
279: new ExpressionContext[] { new ExpressionContext(
280: PointcutType.STATIC_INITIALIZATION,
281: classInfo.staticInitializer(),
282: classInfo) });
283: if (!filterForStaticinitialization) {
284: filterForStaticinitialization = !hasPointcut(
285: definitions, new ExpressionContext(
286: PointcutType.STATIC_INITIALIZATION,
287: classInfo.staticInitializer(),
288: classInfo));
289: }
290:
291: // prepare ctor call jp
292: final ClassReader crLookahead = new ClassReader(
293: bytecode);
294: HashMap newInvocationsByCallerMemberHash = null;
295: if (!filterForCall) {
296: newInvocationsByCallerMemberHash = new HashMap();
297: crLookahead
298: .accept(
299: new ConstructorCallVisitor.LookaheadNewDupInvokeSpecialInstructionClassAdapter(
300: newInvocationsByCallerMemberHash),
301: ClassReader.SKIP_DEBUG
302: | ClassReader.SKIP_FRAMES);
303: }
304:
305: // prepare handler jp, by gathering ALL catch blocks and their exception type
306: List catchLabels = new ArrayList();
307: if (!filterForHandler) {
308: final ClassVisitor cv = new EmptyVisitor();
309: HandlerVisitor.LookaheadCatchLabelsClassAdapter lookForCatches = //
310: new HandlerVisitor.LookaheadCatchLabelsClassAdapter(
311: cv, loader, classInfo, context, catchLabels);
312: // we must visit exactly as we will do further on with debug info (that produces extra labels)
313: final ClassReader crLookahead2 = new ClassReader(
314: bytecode);
315: crLookahead2.accept(lookForCatches,
316: ClassReader.SKIP_FRAMES);
317: }
318:
319: // gather wrapper methods to support multi-weaving
320: // skip annotations visit and debug info by using the lookahead read-only classreader
321: Set addedMethods = new HashSet();
322: crLookahead.accept(new AlreadyAddedMethodAdapter(
323: addedMethods), ClassReader.SKIP_DEBUG
324: | ClassReader.SKIP_FRAMES);
325:
326: // ------------------------------------------------
327: // -- Phase 1 -- type change (ITDs)
328: final ClassReader readerPhase1 = new ClassReader(
329: bytecode);
330: final ClassWriter writerPhase1 = new ClassWriter(
331: readerPhase1, ClassWriter.COMPUTE_MAXS);
332: ClassVisitor reversedChainPhase1 = new AddMixinMethodsVisitor(
333: writerPhase1, classInfo, context, addedMethods);
334: reversedChainPhase1 = new AddInterfaceVisitor(
335: reversedChainPhase1, classInfo, context);
336: readerPhase1.accept(reversedChainPhase1,
337: ClassReader.SKIP_FRAMES);
338: context.setCurrentBytecode(writerPhase1.toByteArray());
339:
340: // ------------------------------------------------
341: // update the class info with new ITDs
342: classInfo = AsmClassInfo.newClassInfo(context
343: .getCurrentBytecode(), loader);
344:
345: // ------------------------------------------------
346: // -- Phase 2 -- advice
347: final ClassReader readerPhase2 = new ClassReader(
348: context.getCurrentBytecode());
349: final ClassWriter writerPhase2 = new ClassWriter(
350: readerPhase2, ClassWriter.COMPUTE_MAXS);
351: ClassVisitor reversedChainPhase2 = new InstanceLevelAspectVisitor(
352: writerPhase2, classInfo, context);
353: reversedChainPhase2 = new MethodExecutionVisitor(
354: reversedChainPhase2, classInfo, context,
355: addedMethods);
356: reversedChainPhase2 = new ConstructorBodyVisitor(
357: reversedChainPhase2, classInfo, context,
358: addedMethods);
359: if (!filterForStaticinitialization) {
360: reversedChainPhase2 = new StaticInitializationVisitor(
361: reversedChainPhase2, context, addedMethods);
362: }
363: reversedChainPhase2 = new HandlerVisitor(
364: reversedChainPhase2, context, catchLabels);
365: if (!filterForCall) {
366: reversedChainPhase2 = new MethodCallVisitor(
367: reversedChainPhase2, loader, classInfo,
368: context);
369: reversedChainPhase2 = new ConstructorCallVisitor(
370: reversedChainPhase2, loader, classInfo,
371: context, newInvocationsByCallerMemberHash);
372: }
373: if (!filterForGetSet) {
374: reversedChainPhase2 = new FieldSetFieldGetVisitor(
375: reversedChainPhase2, loader, classInfo,
376: context);
377: }
378: reversedChainPhase2 = new LabelToLineNumberVisitor(
379: reversedChainPhase2, context);
380:
381: readerPhase2.accept(reversedChainPhase2,
382: ClassReader.SKIP_FRAMES);
383: context.setCurrentBytecode(writerPhase2.toByteArray());
384:
385: // ------------------------------------------------
386: // -- AW Finalization -- JP init code and wrapper methods
387: if (context.isAdvised()) {
388: ClassReader readerPhase3 = new ClassReader(context
389: .getCurrentBytecode());
390: final ClassWriter writerPhase3 = new ClassWriter(
391: readerPhase3, ClassWriter.COMPUTE_MAXS);
392: ClassVisitor reversedChainPhase3 = new AddWrapperVisitor(
393: writerPhase3, context, addedMethods);
394: reversedChainPhase3 = new JoinPointInitVisitor(
395: reversedChainPhase3, context);
396: readerPhase3.accept(reversedChainPhase3,
397: ClassReader.SKIP_FRAMES);
398: context.setCurrentBytecode(writerPhase3
399: .toByteArray());
400: }
401: }
402:
403: // ------------------------------------------------
404: // -- Phase DSO -- DSO clustering
405: if (hasCustomAdapter) {
406: ClassAdapterFactory factory = m_configHelper
407: .getCustomAdapter(classInfo);
408: final ClassReader reader = new ClassReader(context
409: .getCurrentBytecode());
410: final ClassWriter writer = new ClassWriter(reader,
411: ClassWriter.COMPUTE_MAXS);
412: ClassVisitor adapter = factory.create(writer, context
413: .getLoader());
414: reader.accept(adapter, ClassReader.SKIP_FRAMES);
415: context.setCurrentBytecode(writer.toByteArray());
416: }
417:
418: if (isDsoAdaptable) {
419: final ClassReader dsoReader = new ClassReader(context
420: .getCurrentBytecode());
421: final ClassWriter dsoWriter = new ClassWriter(
422: dsoReader, ClassWriter.COMPUTE_MAXS);
423: ClassVisitor dsoVisitor = m_configHelper
424: .createClassAdapterFor(dsoWriter, classInfo,
425: m_logger, loader);
426: try {
427: dsoReader.accept(dsoVisitor,
428: ClassReader.SKIP_FRAMES);
429: context.setCurrentBytecode(dsoWriter.toByteArray());
430: } catch (TCLogicalSubclassNotPortableException e) {
431: List l = new ArrayList(1);
432: l.add(e.getSuperClassName());
433: m_logger.subclassOfLogicallyManagedClasses(e
434: .getClassName(), l);
435: }
436:
437: // CDV-237
438: final String[] missingRoots = m_configHelper
439: .getMissingRootDeclarations(classInfo);
440: for (int i = 0; i < missingRoots.length; i++) {
441: String MESSAGE = "The root expression ''{0}'' meant for the class ''{1}'' "
442: + "has no effect, make sure that it is a valid expression "
443: + "and that it is spelled correctly.";
444: Object[] info = { missingRoots[i],
445: classInfo.getName() };
446: String message = MessageFormat
447: .format(MESSAGE, info);
448: consoleLogger.warn(message);
449: }
450: }
451:
452: // ------------------------------------------------
453: // -- Generic finalization -- serialVersionUID
454: if (context.isAdvised() || isDsoAdaptable) {
455: if (ClassInfoHelper.implements Interface(classInfo,
456: "java.io.Serializable")) {
457: ClassReader readerPhase3 = new ClassReader(context
458: .getCurrentBytecode());
459: final ClassWriter writerPhase3 = new ClassWriter(
460: readerPhase3, ClassWriter.COMPUTE_MAXS);
461: readerPhase3.accept(new SafeSerialVersionUIDAdder(
462: writerPhase3), ClassReader.SKIP_FRAMES);
463: context.setCurrentBytecode(writerPhase3
464: .toByteArray());
465: }
466: }
467:
468: AdaptedClassDumper.INSTANCE.write(className, context
469: .getCurrentBytecode());
470: } catch (Throwable t) {
471: t.printStackTrace();
472: throw new WrappedRuntimeException(t);
473: }
474: }
475:
476: private boolean isInstrumentedByDSO(ClassInfo classInfo) {
477: ClassInfo[] interfaces = classInfo.getInterfaces();
478: for (int i = 0; i < interfaces.length; i++) {
479: if (interfaces[i].getName().equals(
480: "com.tc.object.bytecode.TransparentAccess")) {
481: return true;
482: }
483: }
484: return false;
485: }
486:
487: /**
488: * Filters out the classes that are not eligible for transformation.
489: *
490: * @param definitions the definitions
491: * @param ctxs an array with the contexts
492: * @param classInfo the class to filter
493: * @return boolean true if the class should be filtered out
494: */
495: private static boolean classFilter(final Set definitions,
496: final ExpressionContext[] ctxs, final ClassInfo classInfo) {
497: if (classInfo.isInterface()) {
498: return true;
499: }
500: for (Iterator defs = definitions.iterator(); defs.hasNext();) {
501: if (!classFilter((SystemDefinition) defs.next(), ctxs,
502: classInfo)) {
503: return false;
504: }
505: }
506: return true;
507: }
508:
509: /**
510: * Filters out the classes that are not eligible for transformation.
511: *
512: * @param definition the definition
513: * @param ctxs an array with the contexts
514: * @param classInfo the class to filter
515: * @return boolean true if the class should be filtered out
516: * @TODO: when a class had execution pointcut that were removed it must be unweaved, thus not filtered out How to
517: * handle that? cache lookup? or custom class level attribute ?
518: */
519: private static boolean classFilter(
520: final SystemDefinition definition,
521: final ExpressionContext[] ctxs, final ClassInfo classInfo) {
522: if (classInfo.isInterface()) {
523: return true;
524: }
525: String className = classInfo.getName();
526: if (definition.inExcludePackage(className)) {
527: return true;
528: }
529: if (!definition.inIncludePackage(className)) {
530: return true;
531: }
532: if (definition.isAdvised(ctxs)) {
533: return false;
534: }
535: if (definition.hasMixin(ctxs)) {
536: return false;
537: }
538: if (definition.hasIntroducedInterface(ctxs)) {
539: return false;
540: }
541: return !definition.inPreparePackage(className);
542: }
543:
544: private static boolean classFilterFor(final Set definitions,
545: final ExpressionContext[] ctxs) {
546: for (Iterator defs = definitions.iterator(); defs.hasNext();) {
547: if (!classFilterFor((SystemDefinition) defs.next(), ctxs)) {
548: return false;
549: }
550: }
551: return true;
552: }
553:
554: private static boolean classFilterFor(
555: final SystemDefinition definition,
556: final ExpressionContext[] ctxs) {
557: return !definition.isAdvised(ctxs);
558: }
559:
560: private static boolean hasPointcut(final Set definitions,
561: final ExpressionContext ctx) {
562: for (Iterator defs = definitions.iterator(); defs.hasNext();) {
563: if (hasPointcut((SystemDefinition) defs.next(), ctx)) {
564: return true;
565: }
566: }
567: return false;
568: }
569:
570: private static boolean hasPointcut(
571: final SystemDefinition definition,
572: final ExpressionContext ctx) {
573: return definition.hasPointcut(ctx);
574: }
575: }
|