diff options
author | Andy Clement <aclement@pivotal.io> | 2019-01-25 15:49:50 -0800 |
---|---|---|
committer | Andy Clement <aclement@pivotal.io> | 2019-01-25 15:49:50 -0800 |
commit | 38a19ea6a0b864447883328fe1103fd991a308f0 (patch) | |
tree | 78736cef5e004315a6cdbe4071de92c10c663353 /loadtime/src/main | |
parent | b30dde96344652cf514342b0ca597e37fe9181a0 (diff) | |
download | aspectj-38a19ea6a0b864447883328fe1103fd991a308f0.tar.gz aspectj-38a19ea6a0b864447883328fe1103fd991a308f0.zip |
mavenizing loadtime - wip
Diffstat (limited to 'loadtime/src/main')
9 files changed, 3297 insertions, 0 deletions
diff --git a/loadtime/src/main/java/org/aspectj/weaver/loadtime/Aj.java b/loadtime/src/main/java/org/aspectj/weaver/loadtime/Aj.java new file mode 100644 index 000000000..652ea0932 --- /dev/null +++ b/loadtime/src/main/java/org/aspectj/weaver/loadtime/Aj.java @@ -0,0 +1,375 @@ +/******************************************************************************* + * Copyright (c) 2005 Contributors. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution and is available at + * http://eclipse.org/legal/epl-v10.html + * + * Contributors: + * Alexandre Vasseur initial implementation + *******************************************************************************/ +package org.aspectj.weaver.loadtime; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; + +import org.aspectj.bridge.context.CompilationAndWeavingContext; +import org.aspectj.weaver.Dump; +import org.aspectj.weaver.tools.Trace; +import org.aspectj.weaver.tools.TraceFactory; +import org.aspectj.weaver.tools.WeavingAdaptor; +import org.aspectj.weaver.tools.cache.SimpleCache; +import org.aspectj.weaver.tools.cache.SimpleCacheFactory; + +/** + * Adapter between the generic class pre processor interface and the AspectJ weaver Load time weaving consistency relies on + * Bcel.setRepository + * + * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a> + */ +public class Aj implements ClassPreProcessor { + + private IWeavingContext weavingContext; + public static SimpleCache laCache=SimpleCacheFactory.createSimpleCache(); + + /** + * References are added to this queue when their associated classloader is removed, and once on here that indicates that we + * should tidy up the adaptor map and remove the adaptor (weaver) from the map we are maintaining from adaptorkey > adaptor + * (weaver) + */ + private static ReferenceQueue adaptorQueue = new ReferenceQueue(); + + private static Trace trace = TraceFactory.getTraceFactory().getTrace(Aj.class); + + public Aj() { + this(null); + } + + public Aj(IWeavingContext context) { + if (trace.isTraceEnabled()) + trace.enter("<init>", this, new Object[] { context, getClass().getClassLoader() }); + this.weavingContext = context; + if (trace.isTraceEnabled()) + trace.exit("<init>"); + } + + /** + * Initialization + */ + @Override + public void initialize() { + + } + + private final static String deleLoader = "sun.reflect.DelegatingClassLoader"; + private final static String deleLoader2 = "jdk.internal.reflect.DelegatingClassLoader"; // On JDK11+ + + @Override + public byte[] preProcess(String className, byte[] bytes, ClassLoader loader, ProtectionDomain protectionDomain) { + if (loader == null || className == null || + loader.getClass().getName().equals(deleLoader) || loader.getClass().getName().equals(deleLoader2)) { + // skip boot loader, null classes (hibernate), or those from a reflection loader + return bytes; + } + + if (loadersToSkip != null) { + // Check whether to reject it + if (loadersToSkip.contains(loader.getClass().getName())) { +// System.out.println("debug: no weaver created for loader '"+loader.getClass().getName()+"'"); + return bytes; + } + } + + if (trace.isTraceEnabled()) + trace.enter("preProcess", this, new Object[] { className, bytes, loader }); + if (trace.isTraceEnabled()) + trace.event("preProcess", this, new Object[] { loader.getParent(), Thread.currentThread().getContextClassLoader() }); + + try { + synchronized (loader) { + + if (SimpleCacheFactory.isEnabled()) { + byte[] cacheBytes= laCache.getAndInitialize(className, bytes,loader,protectionDomain); + if (cacheBytes!=null){ + return cacheBytes; + } + } + + WeavingAdaptor weavingAdaptor = WeaverContainer.getWeaver(loader, weavingContext); + if (weavingAdaptor == null) { + if (trace.isTraceEnabled()) + trace.exit("preProcess"); + return bytes; + } + try { + weavingAdaptor.setActiveProtectionDomain(protectionDomain); + byte[] newBytes = weavingAdaptor.weaveClass(className, bytes, false); + Dump.dumpOnExit(weavingAdaptor.getMessageHolder(), true); + if (trace.isTraceEnabled()) + trace.exit("preProcess", newBytes); + if (SimpleCacheFactory.isEnabled()) { + laCache.put(className, bytes, newBytes); + } + return newBytes; + } finally { + weavingAdaptor.setActiveProtectionDomain(null); + } + } + + /* Don't like to do this but JVMTI swallows all exceptions */ + } catch (Throwable th) { + trace.error(className, th); + Dump.dumpWithException(th); + // FIXME AV wondering if we should have the option to fail (throw runtime exception) here + // would make sense at least in test f.e. see TestHelper.handleMessage() + if (trace.isTraceEnabled()) + trace.exit("preProcess", th); + return bytes; + } finally { + CompilationAndWeavingContext.resetForThread(); + } + } + + /** + * An AdaptorKey is a WeakReference wrapping a classloader reference that will enqueue to a specified queue when the classloader + * is GC'd. Since the AdaptorKey is used as a key into a hashmap we need to give it a non-varying hashcode/equals + * implementation, and we need that hashcode not to vary even when the internal referent has been GC'd. The hashcode is + * calculated on creation of the AdaptorKey based on the loader instance that it is wrapping. This means even when the referent + * is gone we can still use the AdaptorKey and it will 'point' to the same place as it always did. + */ + private static class AdaptorKey extends WeakReference { + + private final int loaderHashCode, sysHashCode, hashValue; + private final String loaderClass; + + public AdaptorKey(ClassLoader loader) { + super(loader, adaptorQueue); + loaderHashCode = loader.hashCode(); + sysHashCode = System.identityHashCode(loader); + loaderClass = loader.getClass().getName(); + hashValue = loaderHashCode + sysHashCode + loaderClass.hashCode(); + } + + public ClassLoader getClassLoader() { + ClassLoader instance = (ClassLoader) get(); + // Assert instance!=null - shouldn't be asked for after a GC of the referent has occurred ! + return instance; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof AdaptorKey)) { + return false; + } + AdaptorKey other = (AdaptorKey) obj; + return (other.loaderHashCode == loaderHashCode) + && (other.sysHashCode == sysHashCode) + && loaderClass.equals(other.loaderClass); + } + + @Override + public int hashCode() { + return hashValue; + } + + } + + /** + * The reference queue is only processed when a request is made for a weaver adaptor. This means there can be one or two stale + * weavers left around. If the user knows they have finished all their weaving, they might wish to call removeStaleAdaptors + * which will process anything left on the reference queue containing adaptorKeys for garbage collected classloaders. + * + * @param displayProgress produce System.err info on the tidying up process + * @return number of stale weavers removed + */ + public static int removeStaleAdaptors(boolean displayProgress) { + int removed = 0; + synchronized (WeaverContainer.weavingAdaptors) { + if (displayProgress) { + System.err.println("Weaver adaptors before queue processing:"); + Map<AdaptorKey,ExplicitlyInitializedClassLoaderWeavingAdaptor> m = WeaverContainer.weavingAdaptors; + Set<AdaptorKey> keys = m.keySet(); + for (Iterator<AdaptorKey> iterator = keys.iterator(); iterator.hasNext();) { + Object object = iterator.next(); + System.err.println(object + " = " + WeaverContainer.weavingAdaptors.get(object)); + } + } + Object o = adaptorQueue.poll(); + while (o != null) { + if (displayProgress) + System.err.println("Processing referencequeue entry " + o); + AdaptorKey wo = (AdaptorKey) o; + boolean didit = WeaverContainer.weavingAdaptors.remove(wo) != null; + if (didit) { + removed++; + } else { + throw new RuntimeException("Eh?? key=" + wo); + } + if (displayProgress) + System.err.println("Removed? " + didit); + o = adaptorQueue.poll(); + } + if (displayProgress) { + System.err.println("Weaver adaptors after queue processing:"); + Map<AdaptorKey,ExplicitlyInitializedClassLoaderWeavingAdaptor> m = WeaverContainer.weavingAdaptors; + Set<AdaptorKey> keys = m.keySet(); + for (Iterator<AdaptorKey> iterator = keys.iterator(); iterator.hasNext();) { + Object object = iterator.next(); + System.err.println(object + " = " + WeaverContainer.weavingAdaptors.get(object)); + } + } + } + return removed; + } + + /** + * @return the number of entries still in the weavingAdaptors map + */ + public static int getActiveAdaptorCount() { + return WeaverContainer.weavingAdaptors.size(); + } + + /** + * Process the reference queue that contains stale AdaptorKeys - the keys are put on the queue when their classloader referent + * is garbage collected and so the associated adaptor (weaver) should be removed from the map + */ + public static void checkQ() { + synchronized (adaptorQueue) { + Object o = adaptorQueue.poll(); + while (o != null) { + AdaptorKey wo = (AdaptorKey) o; + // boolean removed = + WeaverContainer.weavingAdaptors.remove(wo); + // DBG System.err.println("Evicting key " + wo + " = " + didit); + o = adaptorQueue.poll(); + } + } + } + + public static List<String> loadersToSkip = null; + + static { + // pr271840 - touch the types early and outside the locks + new ExplicitlyInitializedClassLoaderWeavingAdaptor(new ClassLoaderWeavingAdaptor()); + try { + String loadersToSkipProperty = System.getProperty("aj.weaving.loadersToSkip",""); + StringTokenizer st = new StringTokenizer(loadersToSkipProperty, ","); + if (loadersToSkipProperty != null && loadersToSkip == null) { + if (st.hasMoreTokens()) { +// System.out.println("aj.weaving.loadersToSkip is set. Skipping loaders: '"+loadersToSkipProperty+"'"); + loadersToSkip = new ArrayList<String>(); + } + while (st.hasMoreTokens()) { + String nextLoader = st.nextToken(); + loadersToSkip.add(nextLoader); + } + } + } catch (Exception e) { + // Likely security issue related to property access... + } + } + + /** + * Cache of weaver There is one weaver per classloader + */ + static class WeaverContainer { + + final static Map<AdaptorKey,ExplicitlyInitializedClassLoaderWeavingAdaptor> weavingAdaptors = + Collections.synchronizedMap(new HashMap<AdaptorKey,ExplicitlyInitializedClassLoaderWeavingAdaptor>()); + + static WeavingAdaptor getWeaver(ClassLoader loader, IWeavingContext weavingContext) { + ExplicitlyInitializedClassLoaderWeavingAdaptor adaptor = null; + AdaptorKey adaptorKey = new AdaptorKey(loader); + + String loaderClassName = loader.getClass().getName(); + + synchronized (weavingAdaptors) { + checkQ(); + if (loader.equals(myClassLoader)){ + adaptor = myClassLoaderAdaptor; + } else { + adaptor = weavingAdaptors.get(adaptorKey); + } + if (adaptor == null) { + // create it and put it back in the weavingAdaptors map but avoid any kind of instantiation + // within the synchronized block + ClassLoaderWeavingAdaptor weavingAdaptor = new ClassLoaderWeavingAdaptor(); + adaptor = new ExplicitlyInitializedClassLoaderWeavingAdaptor(weavingAdaptor); + if(myClassLoaderAdaptor == null && loader.equals(myClassLoader)){ + myClassLoaderAdaptor = adaptor; + } else { + weavingAdaptors.put(adaptorKey, adaptor); + } + } + } + // perform the initialization + return adaptor.getWeavingAdaptor(loader, weavingContext); + + + } + private static final ClassLoader myClassLoader = WeavingAdaptor.class.getClassLoader(); + private static ExplicitlyInitializedClassLoaderWeavingAdaptor myClassLoaderAdaptor; + } + + + static class ExplicitlyInitializedClassLoaderWeavingAdaptor { + private final ClassLoaderWeavingAdaptor weavingAdaptor; + private boolean isInitialized; + + public ExplicitlyInitializedClassLoaderWeavingAdaptor(ClassLoaderWeavingAdaptor weavingAdaptor) { + this.weavingAdaptor = weavingAdaptor; + this.isInitialized = false; + } + + private void initialize(ClassLoader loader, IWeavingContext weavingContext) { + if (!isInitialized) { + isInitialized = true; + weavingAdaptor.initialize(loader, weavingContext); + } + } + + public ClassLoaderWeavingAdaptor getWeavingAdaptor(ClassLoader loader, IWeavingContext weavingContext) { + initialize(loader, weavingContext); + return weavingAdaptor; + } + } + + /** + * Returns a namespace based on the contest of the aspects available + */ + public String getNamespace(ClassLoader loader) { + ClassLoaderWeavingAdaptor weavingAdaptor = (ClassLoaderWeavingAdaptor) WeaverContainer.getWeaver(loader, weavingContext); + return weavingAdaptor.getNamespace(); + } + + /** + * Check to see if any classes have been generated for a particular classes loader. Calls + * ClassLoaderWeavingAdaptor.generatedClassesExist() + * + * @param loader the class cloder + * @return true if classes have been generated. + */ + public boolean generatedClassesExist(ClassLoader loader) { + return ((ClassLoaderWeavingAdaptor) WeaverContainer.getWeaver(loader, weavingContext)).generatedClassesExistFor(null); + } + + public void flushGeneratedClasses(ClassLoader loader) { + ((ClassLoaderWeavingAdaptor) WeaverContainer.getWeaver(loader, weavingContext)).flushGeneratedClasses(); + } + + @Override + public void prepareForRedefinition(ClassLoader loader, String className) { + ((ClassLoaderWeavingAdaptor) WeaverContainer.getWeaver(loader, weavingContext)).flushGeneratedClassesFor(className); + } + +}
\ No newline at end of file diff --git a/loadtime/src/main/java/org/aspectj/weaver/loadtime/ClassLoaderWeavingAdaptor.java b/loadtime/src/main/java/org/aspectj/weaver/loadtime/ClassLoaderWeavingAdaptor.java new file mode 100644 index 000000000..f67b0f3ad --- /dev/null +++ b/loadtime/src/main/java/org/aspectj/weaver/loadtime/ClassLoaderWeavingAdaptor.java @@ -0,0 +1,1188 @@ +/******************************************************************************* + * Copyright (c) 2005, 2017 Contributors. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution and is available at + * http://eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.aspectj.weaver.loadtime; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; +import java.util.StringTokenizer; + +import org.aspectj.bridge.AbortException; +import org.aspectj.bridge.Constants; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.util.LangUtil; +import org.aspectj.weaver.IUnwovenClassFile; +import org.aspectj.weaver.Lint; +import org.aspectj.weaver.Lint.Kind; +import org.aspectj.weaver.ResolvedType; +import org.aspectj.weaver.UnresolvedType; +import org.aspectj.weaver.World; +import org.aspectj.weaver.bcel.BcelWeakClassLoaderReference; +import org.aspectj.weaver.bcel.BcelWeaver; +import org.aspectj.weaver.bcel.BcelWorld; +import org.aspectj.weaver.bcel.Utility; +import org.aspectj.weaver.loadtime.definition.Definition; +import org.aspectj.weaver.loadtime.definition.DocumentParser; +import org.aspectj.weaver.ltw.LTWWorld; +import org.aspectj.weaver.patterns.PatternParser; +import org.aspectj.weaver.patterns.TypePattern; +import org.aspectj.weaver.tools.GeneratedClassHandler; +import org.aspectj.weaver.tools.Trace; +import org.aspectj.weaver.tools.TraceFactory; +import org.aspectj.weaver.tools.WeavingAdaptor; +import org.aspectj.weaver.tools.cache.WeavedClassCache; + +import sun.misc.Unsafe; + +/** + * @author Alexandre Vasseur + * @author Andy Clement + * @author Abraham Nevado + * @author David Knibb + * @author John Kew + */ +public class ClassLoaderWeavingAdaptor extends WeavingAdaptor { + + private final static String AOP_XML = Constants.AOP_USER_XML + ";" + Constants.AOP_AJC_XML + ";" + Constants.AOP_OSGI_XML; + + private boolean initialized; + + private List<TypePattern> dumpTypePattern = new ArrayList<TypePattern>(); + private boolean dumpBefore = false; + private boolean dumpDirPerClassloader = false; + + private boolean hasExcludes = false; + private List<TypePattern> excludeTypePattern = new ArrayList<TypePattern>(); // anything + private List<String> excludeStartsWith = new ArrayList<String>(); // com.foo..* + private List<String> excludeStarDotDotStar = new ArrayList<String>(); // *..*CGLIB* + private List<String> excludeExactName = new ArrayList<String>(); // com.foo.Bar + private List<String> excludeEndsWith = new ArrayList<String>(); // com.foo.Bar + private List<String[]> excludeSpecial = new ArrayList<String[]>(); + + private boolean hasIncludes = false; + private List<TypePattern> includeTypePattern = new ArrayList<TypePattern>(); + private List<String> includeStartsWith = new ArrayList<String>(); + private List<String> includeExactName = new ArrayList<String>(); + private boolean includeStar = false; + + private List<TypePattern> aspectExcludeTypePattern = new ArrayList<TypePattern>(); + private List<String> aspectExcludeStartsWith = new ArrayList<String>(); + private List<TypePattern> aspectIncludeTypePattern = new ArrayList<TypePattern>(); + private List<String> aspectIncludeStartsWith = new ArrayList<String>(); + + private StringBuffer namespace; + private IWeavingContext weavingContext; + + private List<ConcreteAspectCodeGen> concreteAspects = new ArrayList<ConcreteAspectCodeGen>(); + + private static Trace trace = TraceFactory.getTraceFactory().getTrace(ClassLoaderWeavingAdaptor.class); + + public ClassLoaderWeavingAdaptor() { + super(); + if (trace.isTraceEnabled()) { + trace.enter("<init>", this); + } + if (trace.isTraceEnabled()) { + trace.exit("<init>"); + } + } + + /** + * We don't need a reference to the class loader and using it during construction can cause problems with recursion. It also + * makes sense to supply the weaving context during initialization to. + * + * @deprecated + */ + @Deprecated + public ClassLoaderWeavingAdaptor(final ClassLoader deprecatedLoader, final IWeavingContext deprecatedContext) { + super(); + if (trace.isTraceEnabled()) { + trace.enter("<init>", this, new Object[] { deprecatedLoader, deprecatedContext }); + } + if (trace.isTraceEnabled()) { + trace.exit("<init>"); + } + } + + class SimpleGeneratedClassHandler implements GeneratedClassHandler { + private BcelWeakClassLoaderReference loaderRef; + + SimpleGeneratedClassHandler(ClassLoader loader) { + loaderRef = new BcelWeakClassLoaderReference(loader); + } + + /** + * Callback when we need to define a Closure in the JVM + * + */ + @Override + public void acceptClass (String name, byte[] originalBytes, byte[] wovenBytes) { + try { + if (shouldDump(name.replace('/', '.'), false)) { + dump(name, wovenBytes, false); + } + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + if (activeProtectionDomain != null) { + defineClass(loaderRef.getClassLoader(), name, wovenBytes, activeProtectionDomain); + } else { + defineClass(loaderRef.getClassLoader(), name, wovenBytes); // could be done lazily using the hook + + } + } + } + + public void initialize(final ClassLoader classLoader, IWeavingContext context) { + if (initialized) { + return; + } + + boolean success = true; + + this.weavingContext = context; + if (weavingContext == null) { + weavingContext = new DefaultWeavingContext(classLoader); + } + + createMessageHandler(); + + this.generatedClassHandler = new SimpleGeneratedClassHandler(classLoader); + + List<Definition> definitions = weavingContext.getDefinitions(classLoader, this); + if (definitions.isEmpty()) { + disable(); // TODO maw Needed to ensure messages are flushed + if (trace.isTraceEnabled()) { + trace.exit("initialize", definitions); + } + return; + } + + // TODO when the world works in terms of the context, we can remove the loader + bcelWorld = new LTWWorld(classLoader, weavingContext, getMessageHandler(), null); + + weaver = new BcelWeaver(bcelWorld); + + // register the definitions + success = registerDefinitions(weaver, classLoader, definitions); + if (success) { + + // after adding aspects + weaver.prepareForWeave(); + + enable(); // TODO maw Needed to ensure messages are flushed + success = weaveAndDefineConceteAspects(); + } + + if (success) { + enable(); + } else { + disable(); + bcelWorld = null; + weaver = null; + } + if (WeavedClassCache.isEnabled()) { + initializeCache(classLoader, getAspectClassNames(definitions), generatedClassHandler, getMessageHandler()); + } + + initialized = true; + if (trace.isTraceEnabled()) { + trace.exit("initialize", isEnabled()); + } + } + + /** + * Get the list of all aspects from the defintion list + * @param definitions + * @return + */ + List<String> getAspectClassNames(List<Definition> definitions) { + List<String> aspects = new LinkedList<String>(); + for (Iterator<Definition> it = definitions.iterator(); it.hasNext(); ) { + Definition def = it.next(); + List<String> defAspects = def.getAspectClassNames(); + if (defAspects != null) { + aspects.addAll(defAspects); + } + } + return aspects; + } + + /** + * Load and cache the aop.xml/properties according to the classloader visibility rules + * + * @param loader + */ + List<Definition> parseDefinitions(final ClassLoader loader) { + if (trace.isTraceEnabled()) { + trace.enter("parseDefinitions", this); + } + + List<Definition> definitions = new ArrayList<Definition>(); + try { + info("register classloader " + getClassLoaderName(loader)); + // TODO av underoptimized: we will parse each XML once per CL that see it + + // TODO av dev mode needed ? TBD -Daj5.def=... + if (loader.equals(ClassLoader.getSystemClassLoader())) { + String file = System.getProperty("aj5.def", null); + if (file != null) { + info("using (-Daj5.def) " + file); + definitions.add(DocumentParser.parse((new File(file)).toURI().toURL())); + } + } + + String resourcePath = System.getProperty("org.aspectj.weaver.loadtime.configuration", AOP_XML); + if (trace.isTraceEnabled()) { + trace.event("parseDefinitions", this, resourcePath); + } + + StringTokenizer st = new StringTokenizer(resourcePath, ";"); + + while (st.hasMoreTokens()) { + String nextDefinition = st.nextToken(); + if (nextDefinition.startsWith("file:")) { + try { + String fpath = new URL(nextDefinition).getFile(); + File configFile = new File(fpath); + if (!configFile.exists()) { + warn("configuration does not exist: " + nextDefinition); + } else { + definitions.add(DocumentParser.parse(configFile.toURI().toURL())); + } + } catch (MalformedURLException mue) { + error("malformed definition url: " + nextDefinition); + } + } else { + Enumeration<URL> xmls = weavingContext.getResources(nextDefinition); + // System.out.println("? registerDefinitions: found-aop.xml=" + xmls.hasMoreElements() + ", loader=" + loader); + + Set<URL> seenBefore = new HashSet<URL>(); + while (xmls.hasMoreElements()) { + URL xml = xmls.nextElement(); + if (trace.isTraceEnabled()) { + trace.event("parseDefinitions", this, xml); + } + if (!seenBefore.contains(xml)) { + info("using configuration " + weavingContext.getFile(xml)); + definitions.add(DocumentParser.parse(xml)); + seenBefore.add(xml); + } else { + debug("ignoring duplicate definition: " + xml); + } + } + } + } + if (definitions.isEmpty()) { + info("no configuration found. Disabling weaver for class loader " + getClassLoaderName(loader)); + } + } catch (Exception e) { + definitions.clear(); + warn("parse definitions failed", e); + } + + if (trace.isTraceEnabled()) { + trace.exit("parseDefinitions", definitions); + } + return definitions; + } + + private boolean registerDefinitions(final BcelWeaver weaver, final ClassLoader loader, List<Definition> definitions) { + if (trace.isTraceEnabled()) { + trace.enter("registerDefinitions", this, definitions); + } + boolean success = true; + + try { + registerOptions(weaver, loader, definitions); + registerAspectExclude(weaver, loader, definitions); + registerAspectInclude(weaver, loader, definitions); + success = registerAspects(weaver, loader, definitions); + registerIncludeExclude(weaver, loader, definitions); + registerDump(weaver, loader, definitions); + } catch (Exception ex) { + trace.error("register definition failed", ex); + success = false; + warn("register definition failed", (ex instanceof AbortException) ? null : ex); + } + + if (trace.isTraceEnabled()) { + trace.exit("registerDefinitions", success); + } + return success; + } + + private String getClassLoaderName(ClassLoader loader) { + return weavingContext.getClassLoaderName(); + } + + /** + * Configure the weaver according to the option directives TODO av - don't know if it is that good to reuse, since we only allow + * a small subset of options in LTW + * + * @param weaver + * @param loader + * @param definitions + */ + private void registerOptions(final BcelWeaver weaver, final ClassLoader loader, final List<Definition> definitions) { + StringBuffer allOptions = new StringBuffer(); + for (Definition definition : definitions) { + allOptions.append(definition.getWeaverOptions()).append(' '); + } + + Options.WeaverOption weaverOption = Options.parse(allOptions.toString(), loader, getMessageHandler()); + + // configure the weaver and world + // AV - code duplicates AspectJBuilder.initWorldAndWeaver() + World world = weaver.getWorld(); + setMessageHandler(weaverOption.messageHandler); + world.setXlazyTjp(weaverOption.lazyTjp); + world.setXHasMemberSupportEnabled(weaverOption.hasMember); + world.setTiming(weaverOption.timers, true); + world.setOptionalJoinpoints(weaverOption.optionalJoinpoints); + world.setPinpointMode(weaverOption.pinpoint); + weaver.setReweavableMode(weaverOption.notReWeavable); + if (weaverOption.loadersToSkip != null && weaverOption.loadersToSkip.length() > 0) { + Aj.loadersToSkip = LangUtil.anySplit(weaverOption.loadersToSkip, ","); + } + if (Aj.loadersToSkip != null) { + MessageUtil.info(world.getMessageHandler(),"no longer creating weavers for these classloaders: "+Aj.loadersToSkip); + } + world.performExtraConfiguration(weaverOption.xSet); + world.setXnoInline(weaverOption.noInline); + // AMC - autodetect as per line below, needed for AtAjLTWTests.testLTWUnweavable + world.setBehaveInJava5Way(LangUtil.is15VMOrGreater()); + world.setAddSerialVerUID(weaverOption.addSerialVersionUID); + + /* First load defaults */ + bcelWorld.getLint().loadDefaultProperties(); + + /* Second overlay LTW defaults */ + bcelWorld.getLint().adviceDidNotMatch.setKind(null); + + /* Third load user file using -Xlintfile so that -Xlint wins */ + if (weaverOption.lintFile != null) { + InputStream resource = null; + try { + resource = loader.getResourceAsStream(weaverOption.lintFile); + Exception failure = null; + if (resource != null) { + try { + Properties properties = new Properties(); + properties.load(resource); + world.getLint().setFromProperties(properties); + } catch (IOException e) { + failure = e; + } + } + if (failure != null || resource == null) { + warn("Cannot access resource for -Xlintfile:" + weaverOption.lintFile, failure); + // world.getMessageHandler().handleMessage(new Message( + // "Cannot access resource for -Xlintfile:"+weaverOption.lintFile, + // IMessage.WARNING, + // failure, + // null)); + } + } finally { + try { + resource.close(); + } catch (Throwable t) { + } + } + } + + /* Fourth override with -Xlint */ + if (weaverOption.lint != null) { + if (weaverOption.lint.equals("default")) {// FIXME should be AjBuildConfig.AJLINT_DEFAULT but yetanother deps.. + bcelWorld.getLint().loadDefaultProperties(); + } else { + bcelWorld.getLint().setAll(weaverOption.lint); + if (weaverOption.lint.equals("ignore")) { + bcelWorld.setAllLintIgnored(); + } + } + } + // TODO proceedOnError option + } + + private void registerAspectExclude(final BcelWeaver weaver, final ClassLoader loader, final List<Definition> definitions) { + String fastMatchInfo = null; + for (Definition definition : definitions) { + for (String exclude : definition.getAspectExcludePatterns()) { + TypePattern excludePattern = new PatternParser(exclude).parseTypePattern(); + aspectExcludeTypePattern.add(excludePattern); + fastMatchInfo = looksLikeStartsWith(exclude); + if (fastMatchInfo != null) { + aspectExcludeStartsWith.add(fastMatchInfo); + } + } + } + } + + private void registerAspectInclude(final BcelWeaver weaver, final ClassLoader loader, final List<Definition> definitions) { + String fastMatchInfo = null; + for (Definition definition : definitions) { + for (String include : definition.getAspectIncludePatterns()) { + TypePattern includePattern = new PatternParser(include).parseTypePattern(); + aspectIncludeTypePattern.add(includePattern); + fastMatchInfo = looksLikeStartsWith(include); + if (fastMatchInfo != null) { + aspectIncludeStartsWith.add(fastMatchInfo); + } + } + } + } + + protected void lint(String name, String[] infos) { + Lint lint = bcelWorld.getLint(); + Kind kind = lint.getLintKind(name); + kind.signal(infos, null, null); + } + + @Override + public String getContextId() { + return weavingContext.getId(); + } + + /** + * Register the aspect, following include / exclude rules + * + * @param weaver + * @param loader + * @param definitions + */ + private boolean registerAspects(final BcelWeaver weaver, final ClassLoader loader, final List<Definition> definitions) { + if (trace.isTraceEnabled()) { + trace.enter("registerAspects", this, new Object[] { weaver, loader, definitions }); + } + boolean success = true; + + // TODO: the exclude aspect allow to exclude aspect defined upper in the CL hierarchy - is it what we want ?? + // if not, review the getResource so that we track which resource is defined by which CL + + // iterate aspectClassNames + // exclude if in any of the exclude list + for (Definition definition : definitions) { + for (String aspectClassName : definition.getAspectClassNames()) { + if (acceptAspect(aspectClassName)) { + info("register aspect " + aspectClassName); + // System.err.println("? ClassLoaderWeavingAdaptor.registerAspects() aspectName=" + aspectClassName + + // ", loader=" + loader + ", bundle=" + weavingContext.getClassLoaderName()); + String requiredType = definition.getAspectRequires(aspectClassName); + if (requiredType != null) { + // This aspect expresses that it requires a type to be around, otherwise it should 'switch off' + ((BcelWorld) weaver.getWorld()).addAspectRequires(aspectClassName, requiredType); + } + String definedScope = definition.getScopeForAspect(aspectClassName); + if (definedScope != null) { + ((BcelWorld) weaver.getWorld()).addScopedAspect(aspectClassName, definedScope); + } + // ResolvedType aspect = + weaver.addLibraryAspect(aspectClassName); + + // generate key for SC + if (namespace == null) { + namespace = new StringBuffer(aspectClassName); + } else { + namespace = namespace.append(";").append(aspectClassName); + } + + } else { + // warn("aspect excluded: " + aspectClassName); + lint("aspectExcludedByConfiguration", new String[] { aspectClassName, getClassLoaderName(loader) }); + } + } + } + + // iterate concreteAspects + // exclude if in any of the exclude list - note that the user defined name matters for that to happen + for (Definition definition : definitions) { + for (Definition.ConcreteAspect concreteAspect : definition.getConcreteAspects()) { + if (acceptAspect(concreteAspect.name)) { + info("define aspect " + concreteAspect.name); + ConcreteAspectCodeGen gen = new ConcreteAspectCodeGen(concreteAspect, weaver.getWorld()); + if (!gen.validate()) { + error("Concrete-aspect '" + concreteAspect.name + "' could not be registered"); + success = false; + break; + } + + ((BcelWorld) weaver.getWorld()).addSourceObjectType(Utility.makeJavaClass(concreteAspect.name, gen.getBytes()), + true); + + concreteAspects.add(gen); + + weaver.addLibraryAspect(concreteAspect.name); + + // generate key for SC + if (namespace == null) { + namespace = new StringBuffer(concreteAspect.name); + } else { + namespace = namespace.append(";" + concreteAspect.name); + } + } + } + } + + /* We couldn't register one or more aspects so disable the adaptor */ + if (!success) { + warn("failure(s) registering aspects. Disabling weaver for class loader " + getClassLoaderName(loader)); + } + + /* We didn't register any aspects so disable the adaptor */ + else if (namespace == null) { + success = false; + info("no aspects registered. Disabling weaver for class loader " + getClassLoaderName(loader)); + } + + if (trace.isTraceEnabled()) { + trace.exit("registerAspects", success); + } + return success; + } + + private boolean weaveAndDefineConceteAspects() { + if (trace.isTraceEnabled()) { + trace.enter("weaveAndDefineConceteAspects", this, concreteAspects); + } + boolean success = true; + + for (ConcreteAspectCodeGen gen : concreteAspects) { + String name = gen.getClassName(); + byte[] bytes = gen.getBytes(); + + try { + byte[] newBytes = weaveClass(name, bytes, true); + this.generatedClassHandler.acceptClass(name, bytes, newBytes); + } catch (IOException ex) { + trace.error("weaveAndDefineConceteAspects", ex); + error("exception weaving aspect '" + name + "'", ex); + } + } + + if (trace.isTraceEnabled()) { + trace.exit("weaveAndDefineConceteAspects", success); + } + return success; + } + + /** + * Register the include / exclude filters. We duplicate simple patterns in startWith filters that will allow faster matching + * without ResolvedType + * + * @param weaver + * @param loader + * @param definitions + */ + private void registerIncludeExclude(final BcelWeaver weaver, final ClassLoader loader, final List<Definition> definitions) { + String fastMatchInfo = null; + for (Definition definition : definitions) { + for (Iterator<String> iterator1 = definition.getIncludePatterns().iterator(); iterator1.hasNext();) { + hasIncludes = true; + String include = iterator1.next(); + fastMatchInfo = looksLikeStartsWith(include); + if (fastMatchInfo != null) { + includeStartsWith.add(fastMatchInfo); + } else if (include.equals("*")) { + includeStar = true; + } else if ((fastMatchInfo = looksLikeExactName(include)) != null) { + includeExactName.add(fastMatchInfo); + } else { + TypePattern includePattern = new PatternParser(include).parseTypePattern(); + includeTypePattern.add(includePattern); + } + } + for (Iterator<String> iterator1 = definition.getExcludePatterns().iterator(); iterator1.hasNext();) { + hasExcludes = true; + String exclude = iterator1.next(); + fastMatchInfo = looksLikeStartsWith(exclude); + if (fastMatchInfo != null) { + excludeStartsWith.add(fastMatchInfo); + } else if ((fastMatchInfo = looksLikeStarDotDotStarExclude(exclude)) != null) { + excludeStarDotDotStar.add(fastMatchInfo); + } else if ((fastMatchInfo = looksLikeExactName(exclude)) != null) { + excludeExactName.add(exclude); + } else if ((fastMatchInfo = looksLikeEndsWith(exclude)) != null) { + excludeEndsWith.add(fastMatchInfo); + } else if (exclude + .equals("org.codehaus.groovy..* && !org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsController*")) { + // TODO need a more sophisticated analysis here, to allow for similar situations + excludeSpecial.add(new String[] { "org.codehaus.groovy.", + "org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsController" }); + // for the related test: + // } else if (exclude.equals("testdata..* && !testdata.sub.Oran*")) { + // excludeSpecial.add(new String[] { "testdata.", "testdata.sub.Oran" }); + } else { + TypePattern excludePattern = new PatternParser(exclude).parseTypePattern(); + excludeTypePattern.add(excludePattern); + } + } + } + } + + /** + * Checks if the pattern looks like "*..*XXXX*" and if so returns XXXX. This will enable fast name matching of CGLIB exclusion + * + */ + private String looksLikeStarDotDotStarExclude(String typePattern) { + if (!typePattern.startsWith("*..*")) { + return null; + } + if (!typePattern.endsWith("*")) { + return null; + } + String subPattern = typePattern.substring(4, typePattern.length() - 1); + if (hasStarDot(subPattern, 0)) { + return null; + } + return subPattern.replace('$', '.'); + } + + /** + * Checks if the pattern looks like "com.foo.Bar" - an exact name + */ + private String looksLikeExactName(String typePattern) { + if (hasSpaceAnnotationPlus(typePattern, 0) || typePattern.indexOf("*") != -1) { + return null; + } + return typePattern.replace('$', '.'); + } + + /** + * Checks if the pattern looks like "*Exception" + */ + private String looksLikeEndsWith(String typePattern) { + if (typePattern.charAt(0) != '*') { + return null; + } + if (hasSpaceAnnotationPlus(typePattern, 1) || hasStarDot(typePattern, 1)) { + return null; + } + return typePattern.substring(1).replace('$', '.'); + } + + /** + * Determine if something in the string is going to affect our ability to optimize. Checks for: ' ' '@' '+' + */ + private boolean hasSpaceAnnotationPlus(String string, int pos) { + for (int i = pos, max = string.length(); i < max; i++) { + char ch = string.charAt(i); + if (ch == ' ' || ch == '@' || ch == '+') { + return true; + } + } + return false; + } + + /** + * Determine if something in the string is going to affect our ability to optimize. Checks for: '*' '.' + */ + private boolean hasStarDot(String string, int pos) { + for (int i = pos, max = string.length(); i < max; i++) { + char ch = string.charAt(i); + if (ch == '*' || ch == '.') { + return true; + } + } + return false; + } + + /** + * Checks if the type pattern looks like "com.foo..*" + */ + private String looksLikeStartsWith(String typePattern) { + if (hasSpaceAnnotationPlus(typePattern, 0) || typePattern.charAt(typePattern.length() - 1) != '*') { + return null; + } + // now must looks like with "charsss..*" or "cha.rss..*" etc + // note that "*" and "*..*" won't be fast matched + // and that "charsss.*" will not neither + int length = typePattern.length(); + if (typePattern.endsWith("..*") && length > 3) { + if (typePattern.indexOf("..") == length - 3 // no ".." before last sequence + && typePattern.indexOf('*') == length - 1) { // no earlier '*' + return typePattern.substring(0, length - 2).replace('$', '.'); // "charsss." or "char.rss." etc + } + } + return null; + } + + /** + * Register the dump filter + * + * @param weaver + * @param loader + * @param definitions + */ + private void registerDump(final BcelWeaver weaver, final ClassLoader loader, final List<Definition> definitions) { + for (Definition definition : definitions) { + for (Iterator<String> iterator1 = definition.getDumpPatterns().iterator(); iterator1.hasNext();) { + String dump = iterator1.next(); + TypePattern pattern = new PatternParser(dump).parseTypePattern(); + dumpTypePattern.add(pattern); + } + if (definition.shouldDumpBefore()) { + dumpBefore = true; + } + if (definition.createDumpDirPerClassloader()) { + dumpDirPerClassloader = true; + } + } + } + + /** + * Determine whether a type should be accepted for weaving, by checking it against any includes/excludes. + * + * @param className the name of the type to possibly accept + * @param bytes the bytecode for the type (in case we need to look inside, eg. annotations) + * @return true if it should be accepted for weaving + */ + @Override + protected boolean accept(String className, byte[] bytes) { + + if (!hasExcludes && !hasIncludes) { + return true; + } + + // still try to avoid ResolvedType if we have simple patterns + String fastClassName = className.replace('/', '.'); + for (String excludeStartsWithString : excludeStartsWith) { + if (fastClassName.startsWith(excludeStartsWithString)) { + return false; + } + } + + // Fast exclusion of patterns like: "*..*CGLIB*" + if (!excludeStarDotDotStar.isEmpty()) { + for (String namePiece : excludeStarDotDotStar) { + int index = fastClassName.lastIndexOf('.'); + if (fastClassName.indexOf(namePiece, index + 1) != -1) { + return false; + } + } + } + fastClassName = fastClassName.replace('$', '.'); + + if (!excludeEndsWith.isEmpty()) { + for (String lastPiece : excludeEndsWith) { + if (fastClassName.endsWith(lastPiece)) { + return false; + } + } + } + + // Fast exclusion of exact names + if (!excludeExactName.isEmpty()) { + for (String name : excludeExactName) { + if (fastClassName.equals(name)) { + return false; + } + } + } + + if (!excludeSpecial.isEmpty()) { + for (String[] entry : excludeSpecial) { + String excludeThese = entry[0]; + String exceptThese = entry[1]; + if (fastClassName.startsWith(excludeThese) && !fastClassName.startsWith(exceptThese)) { + return false; + } + } + } + + /* + * Bug 120363 If we have an exclude pattern that cannot be matched using "starts with" then we cannot fast accept + */ + boolean didSomeIncludeMatching = false; + if (excludeTypePattern.isEmpty()) { + if (includeStar) { + return true; + } + if (!includeExactName.isEmpty()) { + didSomeIncludeMatching = true; + for (String exactname : includeExactName) { + if (fastClassName.equals(exactname)) { + return true; + } + } + } + boolean fastAccept = false;// defaults to false if no fast include + for (int i = 0; i < includeStartsWith.size(); i++) { + didSomeIncludeMatching = true; + fastAccept = fastClassName.startsWith(includeStartsWith.get(i)); + if (fastAccept) { + return true; + } + } + // We may have processed all patterns now... check that and return + if (includeTypePattern.isEmpty()) { + return !didSomeIncludeMatching; + } + } + + boolean accept; + try { + ensureDelegateInitialized(className, bytes); + + ResolvedType classInfo = delegateForCurrentClass.getResolvedTypeX(); + + // exclude are "AND"ed + for (TypePattern typePattern : excludeTypePattern) { + if (typePattern.matchesStatically(classInfo)) { + // exclude match - skip + return false; + } + } + // include are "OR"ed + if (includeStar) { + return true; + } + if (!includeExactName.isEmpty()) { + didSomeIncludeMatching = true; + for (String exactname : includeExactName) { + if (fastClassName.equals(exactname)) { + return true; + } + } + } + for (int i = 0; i < includeStartsWith.size(); i++) { + didSomeIncludeMatching = true; + boolean fastaccept = fastClassName.startsWith(includeStartsWith.get(i)); + if (fastaccept) { + return true; + } + } + accept = !didSomeIncludeMatching; // only true if no includes at all + for (TypePattern typePattern : includeTypePattern) { + accept = typePattern.matchesStatically(classInfo); + if (accept) { + break; + } + // goes on if this include did not match ("OR"ed) + } + } finally { + this.bcelWorld.demote(); + } + return accept; + } + + // FIXME we don't use include/exclude of others aop.xml + // this can be nice but very dangerous as well to change that + private boolean acceptAspect(String aspectClassName) { + // avoid ResolvedType if not needed + if (aspectExcludeTypePattern.isEmpty() && aspectIncludeTypePattern.isEmpty()) { + return true; + } + + // still try to avoid ResolvedType if we have simple patterns + // EXCLUDE: if one match then reject + String fastClassName = aspectClassName.replace('/', '.').replace('.', '$'); + for (int i = 0; i < aspectExcludeStartsWith.size(); i++) { + if (fastClassName.startsWith(aspectExcludeStartsWith.get(i))) { + return false; + } + } + // INCLUDE: if one match then accept + for (int i = 0; i < aspectIncludeStartsWith.size(); i++) { + if (fastClassName.startsWith(aspectIncludeStartsWith.get(i))) { + return true; + } + } + + // needs further analysis + ResolvedType classInfo = weaver.getWorld().resolve(UnresolvedType.forName(aspectClassName), true); + // exclude are "AND"ed + for (TypePattern typePattern: aspectExcludeTypePattern) { + if (typePattern.matchesStatically(classInfo)) { + // exclude match - skip + return false; + } + } + // include are "OR"ed + boolean accept = true;// defaults to true if no include + for (TypePattern typePattern: aspectIncludeTypePattern) { + accept = typePattern.matchesStatically(classInfo); + if (accept) { + break; + } + // goes on if this include did not match ("OR"ed) + } + return accept; + } + + @Override + protected boolean shouldDump(String className, boolean before) { + // Don't dump before weaving unless asked to + if (before && !dumpBefore) { + return false; + } + + // avoid ResolvedType if not needed + if (dumpTypePattern.isEmpty()) { + return false; + } + + // TODO AV - optimize for className.startWith only + ResolvedType classInfo = weaver.getWorld().resolve(UnresolvedType.forName(className), true); + // dump + for (Iterator<TypePattern> iterator = dumpTypePattern.iterator(); iterator.hasNext();) { + TypePattern typePattern = iterator.next(); + if (typePattern.matchesStatically(classInfo)) { + // dump match + return true; + } + } + return false; + } + + @Override + protected String getDumpDir() { + if (dumpDirPerClassloader) { + StringBuffer dir = new StringBuffer(); + dir.append("_ajdump").append(File.separator).append(weavingContext.getId()); + return dir.toString(); + } else { + return super.getDumpDir(); + } + } + + /* + * shared classes methods + */ + + /** + * @return Returns the key. + */ + public String getNamespace() { + // System.out.println("ClassLoaderWeavingAdaptor.getNamespace() classloader=" + weavingContext.getClassLoaderName() + + // ", namespace=" + namespace); + if (namespace == null) { + return ""; + } else { + return new String(namespace); + } + } + + /** + * Check to see if any classes are stored in the generated classes cache. Then flush the cache if it is not empty + * + * @param className TODO + * @return true if a class has been generated and is stored in the cache + */ + public boolean generatedClassesExistFor(String className) { + // System.err.println("? ClassLoaderWeavingAdaptor.generatedClassesExist() classname=" + className + ", size=" + + // generatedClasses); + if (className == null) { + return !generatedClasses.isEmpty(); + } else { + return generatedClasses.containsKey(className); + } + } + + /** + * Flush the generated classes cache + */ + public void flushGeneratedClasses() { + // System.err.println("? ClassLoaderWeavingAdaptor.flushGeneratedClasses() generatedClasses=" + generatedClasses); + generatedClasses = new HashMap<String, IUnwovenClassFile>(); + } + + /** + * Remove generated classes based on the supplied className. This will + * remove any entries related to this name - so the class itself plus + * and inner classes. + * @param className a slashed classname (e.g. com/foo/Bar) + */ + public void flushGeneratedClassesFor(String className) { + try { + String dottedClassName = className.replace('/', '.'); + String dottedClassNameDollar = dottedClassName+"$"; // to pickup inner classes + Iterator<Map.Entry<String, IUnwovenClassFile>> iter = generatedClasses.entrySet().iterator(); + while (iter.hasNext()) { + Entry<String, IUnwovenClassFile> next = iter.next(); + String existingGeneratedName = next.getKey(); + if (existingGeneratedName.equals(dottedClassName) || + existingGeneratedName.startsWith(dottedClassNameDollar)) { + iter.remove(); + } + } + } catch (Throwable t) { + new RuntimeException("Unexpected problem tidying up generated classes for "+className,t).printStackTrace(); + } + } + + private Unsafe unsafe; + + private Unsafe getUnsafe() throws NoSuchFieldException, IllegalAccessException { + if (unsafe == null) { + Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe"); + theUnsafeField.setAccessible(true); + return (Unsafe) theUnsafeField.get(null); + } + return unsafe; + } + + private static Method bindTo_Method, invokeWithArguments_Method = null; + private static Object defineClassMethodHandle = null; + + private static Boolean initializedForJava11 = false; + + // In order to let this code compile on earlier versions of Java (8), use reflection to discover the elements + // we need to define classes. + private static synchronized void initializeForJava11() { + if (initializedForJava11) return; + try { + // MethodType defineClassMethodType = MethodType.methodType(Class.class, new Class[]{String.class, byte[].class, int.class, int.class, ProtectionDomain.class}); + Class<?> methodType_Class = Class.forName("java.lang.invoke.MethodType"); + Method methodTypeMethodOnMethodTypeClass = methodType_Class.getDeclaredMethod("methodType", Class.class,Class[].class); + methodTypeMethodOnMethodTypeClass.setAccessible(true); + Object defineClassMethodType = methodTypeMethodOnMethodTypeClass.invoke(null, Class.class, new Class[] {String.class,byte[].class,int.class,int.class,ProtectionDomain.class}); + + // MethodHandles.Lookup methodHandlesLookup = MethodHandles.lookup(); + Class<?> methodHandles_Class = Class.forName("java.lang.invoke.MethodHandles"); + Method lookupMethodOnMethodHandlesClass = methodHandles_Class.getDeclaredMethod("lookup"); + lookupMethodOnMethodHandlesClass.setAccessible(true); + Object methodHandlesLookup = lookupMethodOnMethodHandlesClass.invoke(null); + + // MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(ClassLoader.class, baseLookup); + Class<?> methodHandlesLookup_Class = Class.forName("java.lang.invoke.MethodHandles$Lookup"); + Method privateLookupMethodOnMethodHandlesClass = methodHandles_Class.getDeclaredMethod("privateLookupIn",Class.class,methodHandlesLookup_Class); + privateLookupMethodOnMethodHandlesClass.setAccessible(true); + Object lookup = privateLookupMethodOnMethodHandlesClass.invoke(null, ClassLoader.class, methodHandlesLookup); + + // MethodHandle defineClassMethodHandle = lookup.findVirtual(ClassLoader.class, "defineClass", defineClassMethodType); + Method findVirtual_Method = methodHandlesLookup_Class.getDeclaredMethod("findVirtual", Class.class,String.class,methodType_Class); + findVirtual_Method.setAccessible(true); + defineClassMethodHandle = findVirtual_Method.invoke(lookup, ClassLoader.class, "defineClass",defineClassMethodType); + + // clazz = defineClassMethodHandle.bindTo(loader).invokeWithArguments(name, bytes, 0, bytes.length); + Class<?> methodHandle_Class = Class.forName("java.lang.invoke.MethodHandle"); + bindTo_Method = methodHandle_Class.getDeclaredMethod("bindTo", Object.class); + invokeWithArguments_Method = methodHandle_Class.getDeclaredMethod("invokeWithArguments",Object[].class); + + initializedForJava11 = true; + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void defineClass(ClassLoader loader, String name, byte[] bytes, ProtectionDomain protectionDomain) { + if (trace.isTraceEnabled()) { + trace.enter("defineClass", this, new Object[] { loader, name, bytes }); + } + Object clazz = null; + debug("generating class '" + name + "'"); + if (LangUtil.is11VMOrGreater()) { + try { + if (!initializedForJava11) { + initializeForJava11(); + } + // Do this: clazz = defineClassMethodHandle.bindTo(loader).invokeWithArguments(name, bytes, 0, bytes.length, protectionDomain); + Object o = bindTo_Method.invoke(defineClassMethodHandle,loader); + clazz = invokeWithArguments_Method.invoke(o, new Object[] {new Object[] {name, bytes, 0, bytes.length, protectionDomain}}); + + } catch (Throwable t) { + t.printStackTrace(System.err); + warn("define generated class failed", t); + } + } else { + try { + if (defineClassMethod == null) { + synchronized (lock) { + getUnsafe(); + defineClassMethod = + Unsafe.class.getDeclaredMethod("defineClass", String.class,byte[].class,Integer.TYPE,Integer.TYPE, ClassLoader.class,ProtectionDomain.class); + } + } + defineClassMethod.setAccessible(true); + clazz = defineClassMethod.invoke(getUnsafe(), name,bytes,0,bytes.length,loader,protectionDomain); + } catch (LinkageError le) { + le.printStackTrace(); + // likely thrown due to defining something that already exists? + // Old comments from before moving to Unsafe.defineClass(): + // is already defined (happens for X$ajcMightHaveAspect interfaces since aspects are reweaved) + // TODO maw I don't think this is OK and + } catch (Exception e) { + e.printStackTrace(System.err); + warn("define generated class failed", e); + } + } + + if (trace.isTraceEnabled()) { + trace.exit("defineClass", clazz); + } + } + static Method defineClassMethod; + private static String lock = "lock"; + + +// /* +// This method is equivalent to the following code but use reflection to compile on Java 7: +// MethodHandles.Lookup baseLookup = MethodHandles.lookup(); +// MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(ClassLoader.class, baseLookup); +// MethodHandle defineClassMethodHandle = lookup.findVirtual(ClassLoader.class, "defineClass", defineClassMethodType); +// handle.bindTo(classLoader).invokeWithArguments(className, classBytes, 0, classBytes.length)); +// */ +//@Override +//@SuppressWarnings("unchecked") +//public <T> Class<T> defineClass(ClassLoader classLoader, String className, byte[] classBytes) { +// Object baseLookup = methodHandlesLookup.invoke(null); +// Object lookup = methodHandlesPrivateLookupIn.invoke(null, ClassLoader.class, baseLookup); +// MethodHandle defineClassMethodHandle = (MethodHandle) lookupFindVirtual.invoke(lookup, ClassLoader.class, "defineClass", defineClassMethodType); +// try { +// return Cast.uncheckedCast(defineClassMethodHandle.bindTo(classLoader).invokeWithArguments(className, classBytes, 0, classBytes.length)); +// } catch (Throwable throwable) { +// throw new RuntimeException(throwable); +// return (Class) defineClassMethodHandle.bindTo(classLoader).invokeWithArguments(className, classBytes, 0, classBytes.length); +// } catch (Throwable e) { +// throw new RuntimeException(e); +// } +//} + + private void defineClass(ClassLoader loader, String name, byte[] bytes){ + defineClass(loader,name,bytes,null);//, ProtectionDomain protectionDomain) { + } +// if (trace.isTraceEnabled()) { +// trace.enter("defineClass", this, new Object[] { loader, name, bytes, protectionDomain }); +// } +// Object clazz = null; +// debug("generating class '" + name + "'"); +// try { +// getUnsafe().defineClass(name, bytes, 0, bytes.length, loader, protectionDomain); +// } catch (LinkageError le) { +// // likely thrown due to defining something that already exists? +// // Old comments from before moving to Unsafe.defineClass(): +// // is already defined (happens for X$ajcMightHaveAspect interfaces since aspects are reweaved) +// // TODO maw I don't think this is OK and +// } catch (Exception e) { +// warn("define generated class failed", e); +// } +// +// if (trace.isTraceEnabled()) { +// trace.exit("defineClass", clazz); +// } +// } + +}
\ No newline at end of file diff --git a/loadtime/src/main/java/org/aspectj/weaver/loadtime/ClassPreProcessor.java b/loadtime/src/main/java/org/aspectj/weaver/loadtime/ClassPreProcessor.java new file mode 100644 index 000000000..97348b281 --- /dev/null +++ b/loadtime/src/main/java/org/aspectj/weaver/loadtime/ClassPreProcessor.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2005,2018 Contributors. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution and is available at + * http://eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.aspectj.weaver.loadtime; + +import java.security.ProtectionDomain; + +/** + * Generic class pre processor interface that allows to separate the AspectJ 5 load time weaving from Java 5 JVMTI interfaces for + * further use on Java 1.3 / 1.4 + * + * @author Alexandre Vasseur + * @author Andy Clement + */ +public interface ClassPreProcessor { + + /** + * Post constructor initialization, usually empty + */ + void initialize(); + + byte[] preProcess(String className, byte[] bytes, ClassLoader classLoader, ProtectionDomain protectionDomain); + + void prepareForRedefinition(ClassLoader loader, String className); +}
\ No newline at end of file diff --git a/loadtime/src/main/java/org/aspectj/weaver/loadtime/ConcreteAspectCodeGen.java b/loadtime/src/main/java/org/aspectj/weaver/loadtime/ConcreteAspectCodeGen.java new file mode 100644 index 000000000..15e1e9fd4 --- /dev/null +++ b/loadtime/src/main/java/org/aspectj/weaver/loadtime/ConcreteAspectCodeGen.java @@ -0,0 +1,990 @@ +/******************************************************************************* + * Copyright (c) 2005 Contributors. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution and is available at + * http://eclipse.org/legal/epl-v10.html + * + * Contributors: + * Alexandre Vasseur initial implementation + *******************************************************************************/ +package org.aspectj.weaver.loadtime; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.aspectj.apache.bcel.Constants; +import org.aspectj.apache.bcel.classfile.ConstantPool; +import org.aspectj.apache.bcel.classfile.JavaClass; +import org.aspectj.apache.bcel.classfile.annotation.AnnotationGen; +import org.aspectj.apache.bcel.classfile.annotation.ClassElementValue; +import org.aspectj.apache.bcel.classfile.annotation.ElementValue; +import org.aspectj.apache.bcel.classfile.annotation.NameValuePair; +import org.aspectj.apache.bcel.classfile.annotation.SimpleElementValue; +import org.aspectj.apache.bcel.generic.FieldGen; +import org.aspectj.apache.bcel.generic.InstructionConstants; +import org.aspectj.apache.bcel.generic.InstructionFactory; +import org.aspectj.apache.bcel.generic.InstructionHandle; +import org.aspectj.apache.bcel.generic.InstructionList; +import org.aspectj.apache.bcel.generic.LocalVariableTag; +import org.aspectj.apache.bcel.generic.ObjectType; +import org.aspectj.apache.bcel.generic.Type; +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.Message; +import org.aspectj.weaver.AjAttribute; +import org.aspectj.weaver.AnnotationAJ; +import org.aspectj.weaver.GeneratedReferenceTypeDelegate; +import org.aspectj.weaver.ReferenceType; +import org.aspectj.weaver.ResolvedMember; +import org.aspectj.weaver.ResolvedType; +import org.aspectj.weaver.UnresolvedType; +import org.aspectj.weaver.World; +import org.aspectj.weaver.bcel.BcelAnnotation; +import org.aspectj.weaver.bcel.BcelPerClauseAspectAdder; +import org.aspectj.weaver.bcel.BcelWorld; +import org.aspectj.weaver.bcel.LazyClassGen; +import org.aspectj.weaver.bcel.LazyMethodGen; +import org.aspectj.weaver.loadtime.definition.Definition; +import org.aspectj.weaver.loadtime.definition.Definition.AdviceKind; +import org.aspectj.weaver.loadtime.definition.Definition.DeclareAnnotationKind; +import org.aspectj.weaver.loadtime.definition.Definition.PointcutAndAdvice; +import org.aspectj.weaver.patterns.BasicTokenSource; +import org.aspectj.weaver.patterns.DeclareAnnotation; +import org.aspectj.weaver.patterns.ISignaturePattern; +import org.aspectj.weaver.patterns.ITokenSource; +import org.aspectj.weaver.patterns.PatternParser; +import org.aspectj.weaver.patterns.PerClause; +import org.aspectj.weaver.patterns.TypePattern; + +/** + * Generates bytecode for concrete-aspect. + * <p> + * The concrete aspect is generated annotation style aspect (so traditional Java constructs annotated with our AspectJ annotations). + * As it is built during aop.xml definitions registration we perform the type munging for perclause, ie. aspectOf() artifact + * directly, instead of waiting for it to go thru the weaver (that we are in the middle of configuring). + * + * @author Alexandre Vasseur + * @author Andy Clement + */ +public class ConcreteAspectCodeGen { + + private final static String[] EMPTY_STRINGS = new String[0]; + private final static Type[] EMPTY_TYPES = new Type[0]; + + /** + * Concrete aspect definition we build for + */ + private final Definition.ConcreteAspect concreteAspect; + + /** + * World for which we build for + */ + private final World world; + + /** + * Set to true when all is checks are verified + */ + private boolean isValid = false; + + /** + * The parent aspect, not concretized + */ + private ResolvedType parent; + + /** + * Aspect perClause, used for direct munging of aspectOf artifacts + */ + private PerClause perclause; + + /** + * Bytecode for the generated class + */ + private byte[] bytes; + + /** + * Create a new generator for a concrete aspect + * + * @param concreteAspect the aspect definition + * @param world the related world (for type resolution, etc) + */ + ConcreteAspectCodeGen(Definition.ConcreteAspect concreteAspect, World world) { + this.concreteAspect = concreteAspect; + this.world = world; + } + + /** + * Checks that concrete aspect is valid. + * + * @return true if ok, false otherwise + */ + public boolean validate() { + if (!(world instanceof BcelWorld)) { + reportError("Internal error: world must be of type BcelWorld"); + return false; + } + + // name must be undefined so far + // TODO only convert the name to signature once, probably earlier than this + ResolvedType current = world.lookupBySignature(UnresolvedType.forName(concreteAspect.name).getSignature()); + + if (current != null && !current.isMissing()) { + reportError("Attempt to concretize but chosen aspect name already defined: " + stringify()); + return false; + } + + if (concreteAspect.pointcutsAndAdvice.size() != 0) { + isValid = true; + return true; + } + + if (concreteAspect.declareAnnotations.size()!=0) { + isValid = true; + return true; + } + + // it can happen that extends is null, for precedence only declaration + if (concreteAspect.extend == null && concreteAspect.precedence != null) { + if (concreteAspect.pointcuts.isEmpty()) { + isValid = true; + // m_perClause = new PerSingleton(); + parent = null; + return true;// no need to checks more in that special case + } else { + reportError("Attempt to use nested pointcuts without extends clause: " + stringify()); + return false; + } + } + + String parentAspectName = concreteAspect.extend; + + if (parentAspectName.indexOf("<") != -1) { + // yikes, generic parent + parent = world.resolve(UnresolvedType.forName(parentAspectName), true); + if (parent.isMissing()) { + reportError("Unable to resolve type reference: " + stringify()); + return false; + } + if (parent.isParameterizedType()) { + UnresolvedType[] typeParameters = parent.getTypeParameters(); + for (int i = 0; i < typeParameters.length; i++) { + UnresolvedType typeParameter = typeParameters[i]; + if (typeParameter instanceof ResolvedType && ((ResolvedType) typeParameter).isMissing()) { + reportError("Unablet to resolve type parameter '" + typeParameter.getName() + "' from " + stringify()); + return false; + } + } + } + } else { + parent = world.resolve(concreteAspect.extend, true); + } + // handle inner classes + if (parent.isMissing()) { + // fallback on inner class lookup mechanism + String fixedName = concreteAspect.extend; + int hasDot = fixedName.lastIndexOf('.'); + while (hasDot > 0) { + char[] fixedNameChars = fixedName.toCharArray(); + fixedNameChars[hasDot] = '$'; + fixedName = new String(fixedNameChars); + hasDot = fixedName.lastIndexOf('.'); + parent = world.resolve(UnresolvedType.forName(fixedName), true); + if (!parent.isMissing()) { + break; + } + } + } + + if (parent.isMissing()) { + reportError("Cannot find parent aspect for: " + stringify()); + return false; + } + + // extends must be abstract (allow for special case of Object where just using aspect for deows) + if (!(parent.isAbstract() || parent.equals(ResolvedType.OBJECT))) { + reportError("Attempt to concretize a non-abstract aspect: " + stringify()); + return false; + } + + // m_parent must be aspect (allow for special case of Object where just using aspect for deows) + if (!(parent.isAspect() || parent.equals(ResolvedType.OBJECT))) { + reportError("Attempt to concretize a non aspect: " + stringify()); + return false; + } + + // must have all abstractions defined + List<String> elligibleAbstractions = new ArrayList<String>(); + + Collection<ResolvedMember> abstractMethods = getOutstandingAbstractMethods(parent); + for (ResolvedMember method : abstractMethods) { + if ("()V".equals(method.getSignature())) { + String n = method.getName(); + // Allow for the abstract pointcut being from a code style + // aspect compiled with -1.5 (see test for 128744) + if (n.startsWith("ajc$pointcut")) { + n = n.substring(14); + n = n.substring(0, n.indexOf("$")); + elligibleAbstractions.add(n); + } else if (hasPointcutAnnotation(method)) { + elligibleAbstractions.add(method.getName()); + } else { + // error, an outstanding abstract method that can't be + // concretized in XML + reportError("Abstract method '" + method.toString() + "' cannot be concretized in XML: " + stringify()); + return false; + } + } else { + if (method.getName().startsWith("ajc$pointcut") || hasPointcutAnnotation(method)) { + // it may be a pointcut but it doesn't meet the requirements for XML concretization + reportError("Abstract method '" + + method.toString() + + "' cannot be concretized as a pointcut (illegal signature, must have no arguments, must return void): " + + stringify()); + return false; + } else { + // error, an outstanding abstract method that can't be + // concretized in XML + reportError("Abstract method '" + method.toString() + "' cannot be concretized in XML: " + stringify()); + return false; + } + } + } + List<String> pointcutNames = new ArrayList<String>(); + for (Definition.Pointcut abstractPc : concreteAspect.pointcuts) { + pointcutNames.add(abstractPc.name); + } + for (String elligiblePc : elligibleAbstractions) { + if (!pointcutNames.contains(elligiblePc)) { + reportError("Abstract pointcut '" + elligiblePc + "' not configured: " + stringify()); + return false; + } + } + + if (concreteAspect.perclause != null) { + String perclauseString = concreteAspect.perclause; + if (perclauseString.startsWith("persingleton")) { + } else if (perclauseString.startsWith("percflow")) { + } else if (perclauseString.startsWith("pertypewithin")) { + } else if (perclauseString.startsWith("perthis")) { + } else if (perclauseString.startsWith("pertarget")) { + } else if (perclauseString.startsWith("percflowbelow")) { + } else { + reportError("Unrecognized per clause specified " + stringify()); + return false; + } + } + isValid = true; + return isValid; + } + + private Collection<ResolvedMember> getOutstandingAbstractMethods(ResolvedType type) { + Map<String, ResolvedMember> collector = new HashMap<String, ResolvedMember>(); + // let's get to the top of the hierarchy and then walk down ... + // recording abstract methods then removing + // them if they get defined further down the hierarchy + getOutstandingAbstractMethodsHelper(type, collector); + return collector.values(); + } + + // We are trying to determine abstract methods left over at the bottom of a + // hierarchy that have not been concretized. + private void getOutstandingAbstractMethodsHelper(ResolvedType type, Map<String, ResolvedMember> collector) { + if (type == null) { + return; + } + // Get to the top + if (!type.equals(ResolvedType.OBJECT)) { + if (type.getSuperclass() != null) { + getOutstandingAbstractMethodsHelper(type.getSuperclass(), collector); + } + } + ResolvedMember[] rms = type.getDeclaredMethods(); + if (rms != null) { + for (int i = 0; i < rms.length; i++) { + ResolvedMember member = rms[i]; + String key = member.getName() + member.getSignature(); + if (member.isAbstract()) { + collector.put(key, member); + } else { + collector.remove(key); + } + } + } + } + + /** + * Rebuild the XML snip that defines this concrete aspect, for log error purpose + * + * @return string repr. + */ + private String stringify() { + StringBuffer sb = new StringBuffer("<concrete-aspect name='"); + sb.append(concreteAspect.name); + sb.append("' extends='"); + sb.append(concreteAspect.extend); + sb.append("' perclause='"); + sb.append(concreteAspect.perclause); + sb.append("'/> in aop.xml"); + // TODO needs the extra state from the definition (concretized pointcuts and advice definitions) + return sb.toString(); + } + + private boolean hasPointcutAnnotation(ResolvedMember member) { + AnnotationAJ[] as = member.getAnnotations(); + if (as == null || as.length == 0) { + return false; + } + for (int i = 0; i < as.length; i++) { + if (as[i].getTypeSignature().equals("Lorg/aspectj/lang/annotation/Pointcut;")) { + return true; + } + } + return false; + } + + public String getClassName() { + return concreteAspect.name; + } + + /** + * @return the bytecode for the concrete aspect + */ + public byte[] getBytes() { + if (!isValid) { + throw new RuntimeException("Must validate first"); + } + if (bytes != null) { + return bytes; + } + PerClause.Kind perclauseKind = PerClause.SINGLETON; + PerClause parentPerClause = (parent != null ? parent.getPerClause() : null); + if (parentPerClause != null) { + perclauseKind = parentPerClause.getKind(); + } + String perclauseString = null; + + if (concreteAspect.perclause != null) { + perclauseString = concreteAspect.perclause; + if (perclauseString.startsWith("persingleton")) { + perclauseKind = PerClause.SINGLETON; + } else if (perclauseString.startsWith("percflow")) { + perclauseKind = PerClause.PERCFLOW; + } else if (perclauseString.startsWith("pertypewithin")) { + perclauseKind = PerClause.PERTYPEWITHIN; + } else if (perclauseString.startsWith("perthis")) { + perclauseKind = PerClause.PEROBJECT; + } else if (perclauseString.startsWith("pertarget")) { + perclauseKind = PerClause.PEROBJECT; + } else if (perclauseString.startsWith("percflowbelow")) { + perclauseKind = PerClause.PERCFLOW; + } + } + + // @Aspect //inherit clause from m_parent + // @DeclarePrecedence("....") // if any + // public class xxxName [extends xxxExtends] { + // [@Pointcut(xxxExpression-n) + // public void xxxName-n() {}] + // } + String parentName = "java/lang/Object"; + if (parent != null) { + if (parent.isParameterizedType()) { + parentName = parent.getGenericType().getName().replace('.', '/'); + } else { + parentName = parent.getName().replace('.', '/'); + } + } + // @Aspect public class ... + // TODO AV - we could point to the aop.xml that defines it and use JSR-45 + LazyClassGen cg = new LazyClassGen(concreteAspect.name.replace('.', '/'), parentName, null, Modifier.PUBLIC + + Constants.ACC_SUPER, EMPTY_STRINGS, world); + if (parent != null && parent.isParameterizedType()) { + cg.setSuperClass(parent); + } + if (perclauseString == null) { + AnnotationGen ag = new AnnotationGen(new ObjectType("org/aspectj/lang/annotation/Aspect"), + Collections.<NameValuePair> emptyList(), true, cg.getConstantPool()); + cg.addAnnotation(ag); + } else { + // List elems = new ArrayList(); + List<NameValuePair> elems = new ArrayList<NameValuePair>(); + elems.add(new NameValuePair("value", + new SimpleElementValue(ElementValue.STRING, cg.getConstantPool(), perclauseString), cg.getConstantPool())); + AnnotationGen ag = new AnnotationGen(new ObjectType("org/aspectj/lang/annotation/Aspect"), elems, true, + cg.getConstantPool()); + cg.addAnnotation(ag); + } + if (concreteAspect.precedence != null) { + SimpleElementValue svg = new SimpleElementValue(ElementValue.STRING, cg.getConstantPool(), concreteAspect.precedence); + List<NameValuePair> elems = new ArrayList<NameValuePair>(); + elems.add(new NameValuePair("value", svg, cg.getConstantPool())); + AnnotationGen agprec = new AnnotationGen(new ObjectType("org/aspectj/lang/annotation/DeclarePrecedence"), elems, true, + cg.getConstantPool()); + cg.addAnnotation(agprec); + } + + // default constructor + LazyMethodGen init = new LazyMethodGen(Modifier.PUBLIC, Type.VOID, "<init>", EMPTY_TYPES, EMPTY_STRINGS, cg); + InstructionList cbody = init.getBody(); + cbody.append(InstructionConstants.ALOAD_0); + + cbody.append(cg.getFactory().createInvoke(parentName, "<init>", Type.VOID, EMPTY_TYPES, Constants.INVOKESPECIAL)); + cbody.append(InstructionConstants.RETURN); + cg.addMethodGen(init); + + for (Iterator<Definition.Pointcut> it = concreteAspect.pointcuts.iterator(); it.hasNext();) { + Definition.Pointcut abstractPc = (Definition.Pointcut) it.next(); + // TODO AV - respect visibility instead of opening up as public? + LazyMethodGen mg = new LazyMethodGen(Modifier.PUBLIC, Type.VOID, abstractPc.name, EMPTY_TYPES, EMPTY_STRINGS, cg); + SimpleElementValue svg = new SimpleElementValue(ElementValue.STRING, cg.getConstantPool(), abstractPc.expression); + List<NameValuePair> elems = new ArrayList<NameValuePair>(); + elems.add(new NameValuePair("value", svg, cg.getConstantPool())); + AnnotationGen mag = new AnnotationGen(new ObjectType("org/aspectj/lang/annotation/Pointcut"), elems, true, + cg.getConstantPool()); + AnnotationAJ max = new BcelAnnotation(mag, world); + mg.addAnnotation(max); + + InstructionList body = mg.getBody(); + body.append(InstructionConstants.RETURN); + cg.addMethodGen(mg); + } + + // Construct any defined declare error/warnings + if (concreteAspect.deows.size() > 0) { + int counter = 1; + for (Definition.DeclareErrorOrWarning deow : concreteAspect.deows) { + // Building this: + // @DeclareWarning("call(* javax.sql..*(..)) && !within(org.xyz.daos..*)") + // static final String aMessage = "Only DAOs should be calling JDBC."; + + FieldGen field = new FieldGen(Modifier.FINAL, ObjectType.STRING, "rule" + (counter++), cg.getConstantPool()); + SimpleElementValue svg = new SimpleElementValue(ElementValue.STRING, cg.getConstantPool(), deow.pointcut); + List<NameValuePair> elems = new ArrayList<NameValuePair>(); + elems.add(new NameValuePair("value", svg, cg.getConstantPool())); + AnnotationGen mag = new AnnotationGen(new ObjectType("org/aspectj/lang/annotation/Declare" + + (deow.isError ? "Error" : "Warning")), elems, true, cg.getConstantPool()); + field.addAnnotation(mag); + + field.setValue(deow.message); + cg.addField(field, null); + } + } + + if (concreteAspect.pointcutsAndAdvice.size() > 0) { + int adviceCounter = 1; + for (PointcutAndAdvice paa : concreteAspect.pointcutsAndAdvice) { + generateAdviceMethod(paa, adviceCounter, cg); + adviceCounter++; + } + } + + if (concreteAspect.declareAnnotations.size()>0) { + int decCounter = 1; + for (Definition.DeclareAnnotation da: concreteAspect.declareAnnotations) { + generateDeclareAnnotation(da,decCounter++,cg); + } + } + + // handle the perClause + ReferenceType rt = new ReferenceType(ResolvedType.forName(concreteAspect.name).getSignature(), world); + GeneratedReferenceTypeDelegate grtd = new GeneratedReferenceTypeDelegate(rt); + grtd.setSuperclass(parent); + rt.setDelegate(grtd); + + BcelPerClauseAspectAdder perClauseMunger = new BcelPerClauseAspectAdder(rt, perclauseKind); + perClauseMunger.forceMunge(cg, false); + + // TODO AV - unsafe cast + // register the fresh new class into the world repository as it does not + // exist on the classpath anywhere + JavaClass jc = cg.getJavaClass((BcelWorld) world); + ((BcelWorld) world).addSourceObjectType(jc, true); + + bytes = jc.getBytes(); + return bytes; + } + + /** + * The DeclareAnnotation object encapsulates an method/field/type descriptor and an annotation. This uses a DeclareAnnotation object + * captured from the XML (something like '<declare-annotation field="* field1(..)" annotation="@Annot(a='a',fred=false,'abc')"/>') + * and builds the same construct that would have existed if the code style variant was used. This involves creating a member upon + * which to hang the real annotation and then creating a classfile level attribute indicating a declare annotation is present + * (that includes the signature pattern and a pointer to the real member holding the annotation). + * + */ + private void generateDeclareAnnotation(Definition.DeclareAnnotation da, int decCounter, LazyClassGen cg) { + + // Here is an example member from a code style declare annotation: + //void ajc$declare_at_method_1(); + // RuntimeInvisibleAnnotations: length = 0x6 + // 00 01 00 1B 00 00 + // RuntimeVisibleAnnotations: length = 0x15 + // 00 01 00 1D 00 03 00 1E 73 00 1F 00 20 73 00 21 + // 00 22 73 00 23 + // org.aspectj.weaver.MethodDeclarationLineNumber: length = 0x8 + // 00 00 00 02 00 00 00 16 + // org.aspectj.weaver.AjSynthetic: length = 0x + // + // Code: + // Stack=0, Locals=1, Args_size=1 + // 0: return + + // and at the class level a Declare attribute: + // org.aspectj.weaver.Declare: length = 0x51 + // 05 00 00 00 03 01 00 05 40 41 6E 6E 6F 01 00 17 + // 61 6A 63 24 64 65 63 6C 61 72 65 5F 61 74 5F 6D + // 65 74 68 6F 64 5F 31 01 01 00 00 00 00 05 05 00 + // 08 73 61 79 48 65 6C 6C 6F 00 01 04 00 00 00 00 + // 07 00 00 00 27 00 00 00 34 00 00 00 16 00 00 00 + // 3C + + AnnotationAJ constructedAnnotation = buildDeclareAnnotation_actualAnnotation(cg, da); + if (constructedAnnotation==null) { + return; // error occurred (and was reported), do not continue + } + + String nameComponent = da.declareAnnotationKind.name().toLowerCase(); + String declareName = new StringBuilder("ajc$declare_at_").append(nameComponent).append("_").append(decCounter).toString(); + LazyMethodGen declareMethod = new LazyMethodGen(Modifier.PUBLIC, Type.VOID, declareName, Type.NO_ARGS, EMPTY_STRINGS, cg); + InstructionList declareMethodBody = declareMethod.getBody(); + declareMethodBody.append(InstructionFactory.RETURN); + declareMethod.addAnnotation(constructedAnnotation); + + DeclareAnnotation deca = null; + ITokenSource tokenSource = BasicTokenSource.makeTokenSource(da.pattern,null); + PatternParser pp = new PatternParser(tokenSource); + + if (da.declareAnnotationKind==DeclareAnnotationKind.Method || da.declareAnnotationKind==DeclareAnnotationKind.Field) { + ISignaturePattern isp = (da.declareAnnotationKind==DeclareAnnotationKind.Method?pp.parseCompoundMethodOrConstructorSignaturePattern(true):pp.parseCompoundFieldSignaturePattern()); + deca = new DeclareAnnotation(da.declareAnnotationKind==DeclareAnnotationKind.Method?DeclareAnnotation.AT_METHOD:DeclareAnnotation.AT_FIELD, isp); + } else if (da.declareAnnotationKind==DeclareAnnotationKind.Type) { + TypePattern tp = pp.parseTypePattern(); + deca = new DeclareAnnotation(DeclareAnnotation.AT_TYPE,tp); + } + + deca.setAnnotationMethod(declareName); + deca.setAnnotationString(da.annotation); + AjAttribute attribute = new AjAttribute.DeclareAttribute(deca); + cg.addAttribute(attribute); + cg.addMethodGen(declareMethod); + } + + /** + * Construct the annotation that the declare wants to add to the target. + */ + private AnnotationAJ buildDeclareAnnotation_actualAnnotation(LazyClassGen cg, Definition.DeclareAnnotation da) { + AnnotationGen anno = buildAnnotationFromString(cg.getConstantPool(),cg.getWorld(),da.annotation); + if (anno==null) { + return null; + } else { + AnnotationAJ bcelAnnotation = new BcelAnnotation(anno, world); + return bcelAnnotation; + } + } + + // TODO support array values + // TODO support annotation values + /** + * Build an AnnotationGen object based on a string, for example "@Foo(35,message='string')". Notice single quotes are fine for + * strings as they don't need special handling in the XML. + */ + private AnnotationGen buildAnnotationFromString(ConstantPool cp, World w, String annotationString) { + int paren = annotationString.indexOf('('); + if (paren==-1) { + // the easy case, no values + AnnotationGen aaj = buildBaseAnnotationType(cp,world,annotationString); + return aaj; + } else { + // Discover the name and name/value pairs + String name = annotationString.substring(0,paren); + // break the rest into pieces based on the commas + List<String> values = new ArrayList<String>(); + int pos = paren+1; + int depth = 0; + int len = annotationString.length(); + int start = pos; + while (pos<len) { + char ch = annotationString.charAt(pos); + if (ch==')' && depth==0) { + break; // reached the end + } + if (ch=='(' || ch=='[') { + depth++; + } else if (ch==')' || ch==']') { + depth--; + } + if (ch==',' && depth==0) { + // found a comma at the right level to end a name/value pair + values.add(annotationString.substring(start,pos).trim()); + start=pos+1; + } + pos++; + } + if (start != pos) { + // there is a last bit to add + values.add(annotationString.substring(start,pos).trim()); + } + AnnotationGen aaj = buildBaseAnnotationType(cp,world,name); + if (aaj==null) { + return null; // a check failed + } + String typename = aaj.getTypeName(); + ResolvedType type = UnresolvedType.forName(typename).resolve(world); // shame it isn't retrievable from the anno + ResolvedMember[] rms = type.getDeclaredMethods(); + // Add the values + for (String value: values) { + int equalsIndex = value.indexOf("="); + String key = "value"; + if (value.charAt(0)!='\"' && equalsIndex!=-1) { + key = value.substring(0,equalsIndex).trim(); + value = value.substring(equalsIndex+1).trim(); + } + boolean keyIsOk = false; + for (int m=0;m<rms.length;m++) { + NameValuePair nvp = null; + if (rms[m].getName().equals(key)) { + // found it! + keyIsOk=true; + UnresolvedType rt = rms[m].getReturnType(); + if (rt.isPrimitiveType()) { + switch (rt.getSignature().charAt(0)) { + case 'J': // long + try { + long longValue = Long.parseLong(value); + nvp = new NameValuePair(key,new SimpleElementValue(ElementValue.PRIMITIVE_LONG,cp,longValue),cp); + } catch (NumberFormatException nfe) { + reportError("unable to interpret annotation value '"+value+"' as a long"); + return null; + } + break; + case 'S': // short + try { + short shortValue = Short.parseShort(value); + nvp = new NameValuePair(key,new SimpleElementValue(ElementValue.PRIMITIVE_SHORT,cp,shortValue),cp); + } catch (NumberFormatException nfe) { + reportError("unable to interpret annotation value '"+value+"' as a short"); + return null; + } + break; + case 'F': // float + try { + float floatValue = Float.parseFloat(value); + nvp = new NameValuePair(key,new SimpleElementValue(ElementValue.PRIMITIVE_FLOAT,cp,floatValue),cp); + } catch (NumberFormatException nfe) { + reportError("unable to interpret annotation value '"+value+"' as a float"); + return null; + } + break; + case 'D': // double + try { + double doubleValue = Double.parseDouble(value); + nvp = new NameValuePair(key,new SimpleElementValue(ElementValue.PRIMITIVE_DOUBLE,cp,doubleValue),cp); + } catch (NumberFormatException nfe) { + reportError("unable to interpret annotation value '"+value+"' as a double"); + return null; + } + break; + case 'I': // integer + try { + int intValue = Integer.parseInt(value); + nvp = new NameValuePair(key,new SimpleElementValue(ElementValue.PRIMITIVE_INT,cp,intValue),cp); + } catch (NumberFormatException nfe) { + reportError("unable to interpret annotation value '"+value+"' as an integer"); + return null; + } + break; + case 'B': // byte + try { + byte byteValue = Byte.parseByte(value); + nvp = new NameValuePair(key,new SimpleElementValue(ElementValue.PRIMITIVE_BYTE,cp,byteValue),cp); + } catch (NumberFormatException nfe) { + reportError("unable to interpret annotation value '"+value+"' as a byte"); + return null; + } + break; + case 'C': // char + if (value.length()<2) { + reportError("unable to interpret annotation value '"+value+"' as a char"); + return null; + } + nvp = new NameValuePair(key,new SimpleElementValue(ElementValue.PRIMITIVE_CHAR,cp,value.charAt(1)),cp); + break; + case 'Z': // boolean + try { + boolean booleanValue = Boolean.parseBoolean(value); + nvp = new NameValuePair(key,new SimpleElementValue(ElementValue.PRIMITIVE_BOOLEAN,cp,booleanValue),cp); + } catch (NumberFormatException nfe) { + reportError("unable to interpret annotation value '"+value+"' as a boolean"); + return null; + } + break; + default: + reportError("not yet supporting XML setting of annotation values of type "+rt.getName()); + return null; + } + } else if (UnresolvedType.JL_STRING.equals(rt)) { + if (value.length()<2) { + reportError("Invalid string value specified in annotation string: "+annotationString); + return null; + } + value = value.substring(1,value.length()-1); // trim the quotes off + nvp = new NameValuePair(key,new SimpleElementValue(ElementValue.STRING,cp,value),cp); + } else if (UnresolvedType.JL_CLASS.equals(rt)) { + // format of class string: + // Foo.class + // java.lang.Foo.class + if (value.length()<6) { + reportError("Not a well formed class value for an annotation '"+value+"'"); + return null; + } + String clazz = value.substring(0,value.length()-6); + boolean qualified = clazz.indexOf(".")!=-1; + if (!qualified) { + // if not qualified, have to assume java.lang + clazz = "java.lang."+clazz; + } + nvp = new NameValuePair(key,new ClassElementValue(new ObjectType(clazz),cp),cp); + } + } + if (nvp!=null) { + aaj.addElementNameValuePair(nvp); + } + } + if (!keyIsOk) { + reportError("annotation @"+typename+" does not have a value named "+key); + return null; + } + } + return aaj; + } + } + + private AnnotationGen buildBaseAnnotationType(ConstantPool cp,World world, String typename) { + String annoname = typename; + if (annoname.startsWith("@")) { + annoname= annoname.substring(1); + } + ResolvedType annotationType = UnresolvedType.forName(annoname).resolve(world); + if (!annotationType.isAnnotation()) { + reportError("declare is not specifying an annotation type :"+typename); + return null; + } + if (!annotationType.isAnnotationWithRuntimeRetention()) { + reportError("declare is using an annotation type that does not have runtime retention: "+typename); + return null; + } + List<NameValuePair> elems = new ArrayList<NameValuePair>(); + return new AnnotationGen(new ObjectType(annoname), elems, true, cp); + } + + /** + * Construct the annotation that indicates this is a declare + */ + + /** + * The PointcutAndAdvice object encapsulates an advice kind, a pointcut and names a Java method in a particular class. Generate + * an annotation style advice that has that pointcut whose implementation delegates to the Java method. + */ + private void generateAdviceMethod(PointcutAndAdvice paa, int adviceCounter, LazyClassGen cg) { + + // Check: Verify the class to be called does exist: + ResolvedType delegateClass = world.resolve(UnresolvedType.forName(paa.adviceClass)); + if (delegateClass.isMissing()) { + reportError("Class to invoke cannot be found: '" + paa.adviceClass + "'"); + return; + } + + // Generate a name for this advice, includes advice kind plus a counter + String adviceName = new StringBuilder("generated$").append(paa.adviceKind.toString().toLowerCase()).append("$advice$") + .append(adviceCounter).toString(); + + // Build the annotation that encapsulates the pointcut + AnnotationAJ aaj = buildAdviceAnnotation(cg, paa); + + // Chop up the supplied advice method string into its pieces. + // Example: foo(JoinPoint jp, java.lang.String string) + // JoinPoint and friends are recognized (so dont need fq package) + String method = paa.adviceMethod; + + int paren = method.indexOf("("); + String methodName = method.substring(0, paren); + String signature = method.substring(paren); + + // Check: signature looks ok + if (signature.charAt(0) != '(' || !signature.endsWith(")")) { + reportError("Badly formatted parameter signature: '" + method + "'"); + return; + } + + // Extract parameter types and names + List<Type> paramTypes = new ArrayList<Type>(); + List<String> paramNames = new ArrayList<String>(); + if (signature.charAt(1) != ')') { + // there are parameters to convert into a signature + StringBuilder convertedSignature = new StringBuilder("("); + boolean paramsBroken = false; + int pos = 1; + while (pos < signature.length() && signature.charAt(pos) != ')' && !paramsBroken) { + int nextChunkEndPos = signature.indexOf(',', pos); + if (nextChunkEndPos == -1) { + nextChunkEndPos = signature.indexOf(')', pos); + } + // chunk will be a type plus a space plus a name + String nextChunk = signature.substring(pos, nextChunkEndPos).trim(); + int space = nextChunk.indexOf(" "); + ResolvedType resolvedParamType = null; + if (space == -1) { + // There is no parameter name, hopefully not needed! + if (nextChunk.equals("JoinPoint")) { + nextChunk = "org.aspectj.lang.JoinPoint"; + } else if (nextChunk.equals("JoinPoint.StaticPart")) { + nextChunk = "org.aspectj.lang.JoinPoint$StaticPart"; + } else if (nextChunk.equals("ProceedingJoinPoint")) { + nextChunk = "org.aspectj.lang.ProceedingJoinPoint"; + } + UnresolvedType unresolvedParamType = UnresolvedType.forName(nextChunk); + resolvedParamType = world.resolve(unresolvedParamType); + } else { + String typename = nextChunk.substring(0, space); + if (typename.equals("JoinPoint")) { + typename = "org.aspectj.lang.JoinPoint"; + } else if (typename.equals("JoinPoint.StaticPart")) { + typename = "org.aspectj.lang.JoinPoint$StaticPart"; + } else if (typename.equals("ProceedingJoinPoint")) { + typename = "org.aspectj.lang.ProceedingJoinPoint"; + } + UnresolvedType unresolvedParamType = UnresolvedType.forName(typename); + resolvedParamType = world.resolve(unresolvedParamType); + String paramname = nextChunk.substring(space).trim(); + paramNames.add(paramname); + } + if (resolvedParamType.isMissing()) { + reportError("Cannot find type specified as parameter: '" + nextChunk + "' from signature '" + signature + "'"); + paramsBroken = true; + } + paramTypes.add(Type.getType(resolvedParamType.getSignature())); + convertedSignature.append(resolvedParamType.getSignature()); + pos = nextChunkEndPos + 1; + } + convertedSignature.append(")"); + signature = convertedSignature.toString(); + if (paramsBroken) { + return; + } + } + + Type returnType = Type.VOID; + + // If around advice we must find the actual delegate method and use its return type + if (paa.adviceKind == AdviceKind.Around) { + ResolvedMember[] methods = delegateClass.getDeclaredMethods(); + ResolvedMember found = null; + for (ResolvedMember candidate : methods) { + if (candidate.getName().equals(methodName)) { + UnresolvedType[] cparms = candidate.getParameterTypes(); + if (cparms.length == paramTypes.size()) { + boolean paramsMatch = true; + for (int i = 0; i < cparms.length; i++) { + if (!cparms[i].getSignature().equals(paramTypes.get(i).getSignature())) { + paramsMatch = false; + break; + } + } + if (paramsMatch) { + found = candidate; + break; + } + } + } + } + if (found != null) { + returnType = Type.getType(found.getReturnType().getSignature()); + } else { + reportError("Unable to find method to invoke. In class: " + delegateClass.getName() + " cant find " + + paa.adviceMethod); + return; + } + } + + // Time to construct the method itself: + LazyMethodGen advice = new LazyMethodGen(Modifier.PUBLIC, returnType, adviceName, paramTypes.toArray(new Type[paramTypes + .size()]), EMPTY_STRINGS, cg); + + InstructionList adviceBody = advice.getBody(); + + // Generate code to load the parameters + int pos = 1; // first slot after 'this' + for (int i = 0; i < paramTypes.size(); i++) { + adviceBody.append(InstructionFactory.createLoad(paramTypes.get(i), pos)); + pos += paramTypes.get(i).getSize(); + } + + // Generate the delegate call + adviceBody.append(cg.getFactory().createInvoke(paa.adviceClass, methodName, signature + returnType.getSignature(), + Constants.INVOKESTATIC)); + + // Generate the right return + if (returnType == Type.VOID) { + adviceBody.append(InstructionConstants.RETURN); + } else { + if (returnType.getSignature().length() < 2) { + String sig = returnType.getSignature(); + if (sig.equals("F")) { + adviceBody.append(InstructionConstants.FRETURN); + } else if (sig.equals("D")) { + adviceBody.append(InstructionConstants.DRETURN); + } else if (sig.equals("J")) { + adviceBody.append(InstructionConstants.LRETURN); + } else { + adviceBody.append(InstructionConstants.IRETURN); + } + } else { + adviceBody.append(InstructionConstants.ARETURN); + } + } + // Add the annotation + advice.addAnnotation(aaj); + InstructionHandle start = adviceBody.getStart(); + + // Setup the local variable targeters so that the binding will work + String sig = concreteAspect.name.replace('.', '/'); + start.addTargeter(new LocalVariableTag("L" + sig + ";", "this", 0, start.getPosition())); + if (paramNames.size() > 0) { + for (int i = 0; i < paramNames.size(); i++) { + start.addTargeter(new LocalVariableTag(paramTypes.get(i).getSignature(), paramNames.get(i), i + 1, start + .getPosition())); + } + } + + // Record the new method in the class + cg.addMethodGen(advice); + } + + /** + * For the given PointcutAndAdvice build the correct advice annotation. + */ + private AnnotationAJ buildAdviceAnnotation(LazyClassGen cg, PointcutAndAdvice paa) { + SimpleElementValue svg = new SimpleElementValue(ElementValue.STRING, cg.getConstantPool(), paa.pointcut); + List<NameValuePair> elems = new ArrayList<NameValuePair>(); + elems.add(new NameValuePair("value", svg, cg.getConstantPool())); + AnnotationGen mag = new AnnotationGen(new ObjectType("org/aspectj/lang/annotation/" + paa.adviceKind.toString()), elems, + true, cg.getConstantPool()); + AnnotationAJ aaj = new BcelAnnotation(mag, world); + return aaj; + } + + /** + * Error reporting + * + * @param message + */ + private void reportError(String message) { + world.getMessageHandler().handleMessage(new Message(message, IMessage.ERROR, null, null)); + } +} diff --git a/loadtime/src/main/java/org/aspectj/weaver/loadtime/DefaultMessageHandler.java b/loadtime/src/main/java/org/aspectj/weaver/loadtime/DefaultMessageHandler.java new file mode 100644 index 000000000..6589663fd --- /dev/null +++ b/loadtime/src/main/java/org/aspectj/weaver/loadtime/DefaultMessageHandler.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2005 Contributors. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution and is available at + * http://eclipse.org/legal/epl-v10.html + * + * Contributors: + * Alexandre Vasseur initial implementation + *******************************************************************************/ +package org.aspectj.weaver.loadtime; + +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.AbortException; + +/** + * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a> + */ +public class DefaultMessageHandler implements IMessageHandler { + + boolean isVerbose = false; + boolean isDebug = false; + boolean showWeaveInfo = false; + boolean showWarn = true; + + public boolean handleMessage(IMessage message) throws AbortException { + if (isIgnoring(message.getKind())) { + return false; + } else { + /* + * TODO maw We ship this class but don't use or document it. Changed + * to use stderr instead of stdout to allow improvements to LTW tests. + * Currently many pass whether or not LTW occurs because they are + * already woven. Some changed to check for appropriate weaving messages + * as well as absence of warnings or errors. + */ + return SYSTEM_ERR.handleMessage(message); +// if (message.getKind().isSameOrLessThan(IMessage.INFO)) { +// return SYSTEM_OUT.handleMessage(message); +// } else { +// return SYSTEM_ERR.handleMessage(message); +// } + } + } + + public boolean isIgnoring(IMessage.Kind kind) { + if (kind.equals(IMessage.WEAVEINFO)) { + return !showWeaveInfo; + } + if (kind.isSameOrLessThan(IMessage.INFO)) { + return !isVerbose; + } + if (kind.isSameOrLessThan(IMessage.DEBUG)) { + return !isDebug; + } + return !showWarn; + } + + public void dontIgnore(IMessage.Kind kind) { + if (kind.equals(IMessage.WEAVEINFO)) { + showWeaveInfo = true; + } else if (kind.equals(IMessage.DEBUG)) { + isVerbose = true; + } else if (kind.equals(IMessage.WARNING)) { + showWarn = false; + } + } + + public void ignore(IMessage.Kind kind) { + if (kind.equals(IMessage.WEAVEINFO)) { + showWeaveInfo = false; + } else if (kind.equals(IMessage.DEBUG)) { + isVerbose = false; + } else if (kind.equals(IMessage.WARNING)) { + showWarn = true; + } + } + +} diff --git a/loadtime/src/main/java/org/aspectj/weaver/loadtime/DefaultWeavingContext.java b/loadtime/src/main/java/org/aspectj/weaver/loadtime/DefaultWeavingContext.java new file mode 100644 index 000000000..8e8d9df77 --- /dev/null +++ b/loadtime/src/main/java/org/aspectj/weaver/loadtime/DefaultWeavingContext.java @@ -0,0 +1,137 @@ +/******************************************************************************* + * Copyright (c) 2005 Contributors. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution and is available at + * http://eclipse.org/legal/epl-v10.html + * + * Contributors: + * David Knibb initial implementation + *******************************************************************************/ +package org.aspectj.weaver.loadtime; + +import java.io.IOException; +import java.net.URL; +import java.util.Enumeration; +import java.util.List; + +import org.aspectj.weaver.bcel.BcelWeakClassLoaderReference; +import org.aspectj.weaver.loadtime.definition.Definition; +import org.aspectj.weaver.tools.Trace; +import org.aspectj.weaver.tools.TraceFactory; +import org.aspectj.weaver.tools.WeavingAdaptor; + +/** + * Use in non-OSGi environment + * + * @author David Knibb + */ +public class DefaultWeavingContext implements IWeavingContext { + + protected BcelWeakClassLoaderReference loaderRef; + private String shortName; + + private static Trace trace = TraceFactory.getTraceFactory().getTrace(DefaultWeavingContext.class); + + /** + * Construct a new WeavingContext to use the specified ClassLoader This is the constructor which should be used. + * + * @param loader + */ + public DefaultWeavingContext(ClassLoader loader) { + super(); + this.loaderRef = new BcelWeakClassLoaderReference(loader); + } + + /** + * Same as ClassLoader.getResources() + */ + public Enumeration<URL> getResources(String name) throws IOException { + return getClassLoader().getResources(name); + } + + /** + * @return null as we are not in an OSGi environment (therefore no bundles) + */ + public String getBundleIdFromURL(URL url) { + return ""; + } + + /** + * @return classname@hashcode + */ + public String getClassLoaderName() { + ClassLoader loader = getClassLoader(); + return ((loader != null) ? loader.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(loader)) + : "null"); + } + + public ClassLoader getClassLoader() { + return loaderRef.getClassLoader(); + } + + /** + * @return filename + */ + public String getFile(URL url) { + return url.getFile(); + } + + /** + * @return unqualifiedclassname@hashcode + */ + public String getId() { + if (shortName == null) { + shortName = getClassLoaderName().replace('$', '.'); + int index = shortName.lastIndexOf("."); + if (index != -1) { + shortName = shortName.substring(index + 1); + } + } + return shortName; + } + + public String getSuffix() { + return getClassLoaderName(); + } + + public boolean isLocallyDefined(String classname) { + String asResource = classname.replace('.', '/').concat(".class"); + ClassLoader loader = getClassLoader(); + URL localURL = loader.getResource(asResource); + if (localURL == null) { + return false; + } + + boolean isLocallyDefined = true; + + ClassLoader parent = loader.getParent(); + if (parent != null) { + URL parentURL = parent.getResource(asResource); + if (localURL.equals(parentURL)) { + isLocallyDefined = false; + } + } + return isLocallyDefined; + } + + /** + * Simply call weaving adaptor back to parse aop.xml + * + * @param weaver + * @param loader + */ + public List<Definition> getDefinitions(final ClassLoader loader, final WeavingAdaptor adaptor) { + if (trace.isTraceEnabled()) { + trace.enter("getDefinitions", this, new Object[] { "goo", adaptor }); + } + + List<Definition> definitions = ((ClassLoaderWeavingAdaptor) adaptor).parseDefinitions(loader); + + if (trace.isTraceEnabled()) { + trace.exit("getDefinitions", definitions); + } + return definitions; + } +} diff --git a/loadtime/src/main/java/org/aspectj/weaver/loadtime/JRockitAgent.java b/loadtime/src/main/java/org/aspectj/weaver/loadtime/JRockitAgent.java new file mode 100644 index 000000000..56ad0e958 --- /dev/null +++ b/loadtime/src/main/java/org/aspectj/weaver/loadtime/JRockitAgent.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Matthew Webster - initial implementation + *******************************************************************************/ +package org.aspectj.weaver.loadtime; + +import java.util.Stack; + +import com.bea.jvm.ClassLibrary; +import com.bea.jvm.JVMFactory; + +/** + * BEA JRocket JMAPI agent. + * + * Use "-Xmanagement:class=org.aspectj.weaver.loadtime.JRockitAgent" + */ +public class JRockitAgent implements com.bea.jvm.ClassPreProcessor { + + private ClassPreProcessor preProcessor; + + /* + * This is used to implement the recursion protection offered by JVMTI but not by JRockit JMAPI. I we are called to preProcess a + * class while already preProcessing another we will return immediately + */ + private static ThreadLocalStack stack = new ThreadLocalStack(); + + public JRockitAgent() { + this.preProcessor = new Aj(); + + ClassLibrary cl = JVMFactory.getJVM().getClassLibrary(); + cl.setClassPreProcessor(this); + } + + public byte[] preProcess(ClassLoader loader, String className, byte[] bytes) { + byte[] newBytes = bytes; + + if (stack.empty()) { + stack.push(className); + newBytes = preProcessor.preProcess(className, bytes, loader, null); + stack.pop(); + } + + return newBytes; + } + + private static class ThreadLocalStack extends ThreadLocal { + + public boolean empty() { + Stack stack = (Stack) get(); + return stack.empty(); + } + + public Object peek() { + Object obj = null; + Stack stack = (Stack) get(); + if (!stack.empty()) + obj = stack.peek(); + return obj; + } + + public void push(Object obj) { + Stack stack = (Stack) get(); + if (!stack.empty() && obj == stack.peek()) + throw new RuntimeException(obj.toString()); + stack.push(obj); + } + + public Object pop() { + Stack stack = (Stack) get(); + return stack.pop(); + } + + protected Object initialValue() { + return new Stack(); + } + } + +} diff --git a/loadtime/src/main/java/org/aspectj/weaver/loadtime/Options.java b/loadtime/src/main/java/org/aspectj/weaver/loadtime/Options.java new file mode 100644 index 000000000..0c9611e4f --- /dev/null +++ b/loadtime/src/main/java/org/aspectj/weaver/loadtime/Options.java @@ -0,0 +1,180 @@ +/******************************************************************************* + * Copyright (c) 2005 Contributors. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution and is available at + * http://eclipse.org/legal/epl-v10.html + * + * Contributors: + * Alexandre Vasseur initial implementation + *******************************************************************************/ +package org.aspectj.weaver.loadtime; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.Message; +import org.aspectj.util.LangUtil; + +/** + * A class that hanldes LTW options. Note: AV - I choosed to not reuse AjCompilerOptions and alike since those implies too many + * dependancies on jdt and ajdt modules. + * + * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a> + */ +public class Options { + + private final static String OPTION_15 = "-1.5"; + private final static String OPTION_lazyTjp = "-XlazyTjp"; + private final static String OPTION_noWarn = "-nowarn"; + private final static String OPTION_noWarnNone = "-warn:none"; + private final static String OPTION_proceedOnError = "-proceedOnError"; + private final static String OPTION_verbose = "-verbose"; + private final static String OPTION_debug = "-debug"; + private final static String OPTION_reweavable = "-Xreweavable";// notReweavable is default for LTW + private final static String OPTION_noinline = "-Xnoinline"; + private final static String OPTION_addSerialVersionUID = "-XaddSerialVersionUID"; + private final static String OPTION_hasMember = "-XhasMember"; + private final static String OPTION_pinpoint = "-Xdev:pinpoint"; + private final static String OPTION_showWeaveInfo = "-showWeaveInfo"; + private final static String OPTIONVALUED_messageHandler = "-XmessageHandlerClass:"; + private static final String OPTIONVALUED_Xlintfile = "-Xlintfile:"; + private static final String OPTIONVALUED_Xlint = "-Xlint:"; + private static final String OPTIONVALUED_joinpoints = "-Xjoinpoints:"; + private static final String OPTIONVALUED_Xset = "-Xset:"; + private static final String OPTION_timers = "-timers"; + private static final String OPTIONVALUED_loadersToSkip = "-loadersToSkip:"; + + public static WeaverOption parse(String options, ClassLoader laoder, IMessageHandler imh) { + WeaverOption weaverOption = new WeaverOption(imh); + + if (LangUtil.isEmpty(options)) { + return weaverOption; + } + // the first option wins + List<String> flags = LangUtil.anySplit(options, " "); + Collections.reverse(flags); + + // do a first round on the message handler since it will report the options themselves + for (Iterator<String> iterator = flags.iterator(); iterator.hasNext();) { + String arg = iterator.next(); + if (arg.startsWith(OPTIONVALUED_messageHandler)) { + if (arg.length() > OPTIONVALUED_messageHandler.length()) { + String handlerClass = arg.substring(OPTIONVALUED_messageHandler.length()).trim(); + try { + Class handler = Class.forName(handlerClass, false, laoder); + weaverOption.messageHandler = ((IMessageHandler) handler.newInstance()); + } catch (Throwable t) { + weaverOption.messageHandler.handleMessage(new Message("Cannot instantiate message handler " + handlerClass, + IMessage.ERROR, t, null)); + } + } + } + } + + // configure the other options + for (Iterator<String> iterator = flags.iterator(); iterator.hasNext();) { + String arg = (String) iterator.next(); + if (arg.equals(OPTION_15)) { + weaverOption.java5 = true; + } else if (arg.equalsIgnoreCase(OPTION_lazyTjp)) { + weaverOption.lazyTjp = true; + } else if (arg.equalsIgnoreCase(OPTION_noinline)) { + weaverOption.noInline = true; + } else if (arg.equalsIgnoreCase(OPTION_addSerialVersionUID)) { + weaverOption.addSerialVersionUID = true; + } else if (arg.equalsIgnoreCase(OPTION_noWarn) || arg.equalsIgnoreCase(OPTION_noWarnNone)) { + weaverOption.noWarn = true; + } else if (arg.equalsIgnoreCase(OPTION_proceedOnError)) { + weaverOption.proceedOnError = true; + } else if (arg.equalsIgnoreCase(OPTION_reweavable)) { + weaverOption.notReWeavable = false; + } else if (arg.equalsIgnoreCase(OPTION_showWeaveInfo)) { + weaverOption.showWeaveInfo = true; + } else if (arg.equalsIgnoreCase(OPTION_hasMember)) { + weaverOption.hasMember = true; + } else if (arg.startsWith(OPTIONVALUED_joinpoints)) { + if (arg.length() > OPTIONVALUED_joinpoints.length()) { + weaverOption.optionalJoinpoints = arg.substring(OPTIONVALUED_joinpoints.length()).trim(); + } + } else if (arg.equalsIgnoreCase(OPTION_verbose)) { + weaverOption.verbose = true; + } else if (arg.equalsIgnoreCase(OPTION_debug)) { + weaverOption.debug = true; + } else if (arg.equalsIgnoreCase(OPTION_pinpoint)) { + weaverOption.pinpoint = true; + } else if (arg.startsWith(OPTIONVALUED_messageHandler)) { + // handled in first round + } else if (arg.startsWith(OPTIONVALUED_Xlintfile)) { + if (arg.length() > OPTIONVALUED_Xlintfile.length()) { + weaverOption.lintFile = arg.substring(OPTIONVALUED_Xlintfile.length()).trim(); + } + } else if (arg.startsWith(OPTIONVALUED_Xlint)) { + if (arg.length() > OPTIONVALUED_Xlint.length()) { + weaverOption.lint = arg.substring(OPTIONVALUED_Xlint.length()).trim(); + } + } else if (arg.startsWith(OPTIONVALUED_Xset)) { + if (arg.length() > OPTIONVALUED_Xlint.length()) { + weaverOption.xSet = arg.substring(OPTIONVALUED_Xset.length()).trim(); + } + } else if (arg.equalsIgnoreCase(OPTION_timers)) { + weaverOption.timers = true; + } else if (arg.startsWith(OPTIONVALUED_loadersToSkip)) { + if (arg.length() > OPTIONVALUED_loadersToSkip.length()) { + String value = arg.substring(OPTIONVALUED_loadersToSkip.length()).trim(); + weaverOption.loadersToSkip = value; + } + } else { + weaverOption.messageHandler.handleMessage(new Message("Cannot configure weaver with option '" + arg + + "': unknown option", IMessage.WARNING, null, null)); + } + } + + // refine message handler configuration + if (weaverOption.noWarn) { + weaverOption.messageHandler.ignore(IMessage.WARNING); + } + if (weaverOption.verbose) { + weaverOption.messageHandler.dontIgnore(IMessage.INFO); + } + if (weaverOption.debug) { + weaverOption.messageHandler.dontIgnore(IMessage.DEBUG); + } + if (weaverOption.showWeaveInfo) { + weaverOption.messageHandler.dontIgnore(IMessage.WEAVEINFO); + } + + return weaverOption; + } + + public static class WeaverOption { + boolean java5; + boolean lazyTjp; + boolean hasMember; + boolean timers = false; + String optionalJoinpoints; + boolean noWarn; + boolean proceedOnError; + boolean verbose; + boolean debug; + boolean notReWeavable = true;// default to notReweavable for LTW (faster) + boolean noInline; + boolean addSerialVersionUID; + boolean showWeaveInfo; + boolean pinpoint; + IMessageHandler messageHandler; + String lint; + String lintFile; + String xSet; + String loadersToSkip; + + public WeaverOption(IMessageHandler imh) { + // messageHandler = new DefaultMessageHandler();//default + this.messageHandler = imh; + } + } +} diff --git a/loadtime/src/main/java/org/aspectj/weaver/loadtime/WeavingURLClassLoader.java b/loadtime/src/main/java/org/aspectj/weaver/loadtime/WeavingURLClassLoader.java new file mode 100644 index 000000000..f6b013373 --- /dev/null +++ b/loadtime/src/main/java/org/aspectj/weaver/loadtime/WeavingURLClassLoader.java @@ -0,0 +1,232 @@ +/* ******************************************************************* + * Copyright (c) 2004 IBM Corporation + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Matthew Webster, Adrian Colyer, + * Martin Lippert initial implementation + * Andy Clement + * Abraham Nevado + * ******************************************************************/ + +package org.aspectj.weaver.loadtime; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.CodeSource; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import org.aspectj.bridge.AbortException; +import org.aspectj.weaver.bcel.ExtensibleURLClassLoader; +import org.aspectj.weaver.tools.Trace; +import org.aspectj.weaver.tools.TraceFactory; +import org.aspectj.weaver.tools.WeavingAdaptor; +import org.aspectj.weaver.tools.WeavingClassLoader; + +public class WeavingURLClassLoader extends ExtensibleURLClassLoader implements WeavingClassLoader { + + public static final String WEAVING_CLASS_PATH = "aj.class.path"; + public static final String WEAVING_ASPECT_PATH = "aj.aspect.path"; + + private URL[] aspectURLs; + private WeavingAdaptor adaptor; + private boolean initializingAdaptor; + private Map generatedClasses = new HashMap(); /* String -> byte[] */ + + private static Trace trace = TraceFactory.getTraceFactory().getTrace(WeavingURLClassLoader.class); + + /* + * This constructor is needed when using "-Djava.system.class.loader". + */ + public WeavingURLClassLoader(ClassLoader parent) { + this(getURLs(getClassPath()), getURLs(getAspectPath()), parent); + // System.out.println("? WeavingURLClassLoader.WeavingURLClassLoader()"); + } + + public WeavingURLClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + if (trace.isTraceEnabled()) + trace.enter("<init>", this, new Object[] { urls, parent }); + // System.out.println("WeavingURLClassLoader.WeavingURLClassLoader()"); + if (trace.isTraceEnabled()) + trace.exit("<init>"); + } + + public WeavingURLClassLoader(URL[] classURLs, URL[] aspectURLs, ClassLoader parent) { + super(classURLs, parent); + // System.out.println("> WeavingURLClassLoader.WeavingURLClassLoader() classURLs=" + Arrays.asList(classURLs)); + this.aspectURLs = aspectURLs; + + /* + * If either we nor our parent is using an ASPECT_PATH use a new-style adaptor + */ + if (this.aspectURLs.length > 0 || getParent() instanceof WeavingClassLoader) { + try { + adaptor = new WeavingAdaptor(this); + } catch (ExceptionInInitializerError ex) { + ex.printStackTrace(System.out); + throw ex; + } + } + // System.out.println("< WeavingURLClassLoader.WeavingURLClassLoader() adaptor=" + adaptor); + } + + private static String getAspectPath() { + return System.getProperty(WEAVING_ASPECT_PATH, ""); + } + + private static String getClassPath() { + return System.getProperty(WEAVING_CLASS_PATH, ""); + } + + private static URL[] getURLs(String path) { + List urlList = new ArrayList(); + for (StringTokenizer t = new StringTokenizer(path, File.pathSeparator); t.hasMoreTokens();) { + File f = new File(t.nextToken().trim()); + try { + if (f.exists()) { + URL url = f.toURL(); + if (url != null) + urlList.add(url); + } + } catch (MalformedURLException e) { + } + } + + URL[] urls = new URL[urlList.size()]; + urlList.toArray(urls); + return urls; + } + + protected void addURL(URL url) { + if (adaptor == null) { + createAdaptor(); + } + adaptor.addURL(url); + super.addURL(url); + } + + /** + * Override to weave class using WeavingAdaptor + */ + protected Class defineClass(String name, byte[] b, CodeSource cs) throws IOException { + if (trace.isTraceEnabled()) + trace.enter("defineClass", this, new Object[] { name, b, cs }); + // System.err.println("? WeavingURLClassLoader.defineClass(" + name + ", [" + b.length + "])"); + byte orig[] = b; + /* Avoid recursion during adaptor initialization */ + if (!initializingAdaptor) { + + /* Need to defer creation because of possible recursion during constructor execution */ + if (adaptor == null && !initializingAdaptor) { + createAdaptor(); + } + + try { + b = adaptor.weaveClass(name, b, false); + } catch (AbortException ex) { + trace.error("defineClass", ex); + throw ex; + } catch (Throwable th) { + trace.error("defineClass", th); + } + } + Class clazz; + + // On error, define the original form of the class and log the issue + try { + clazz= super.defineClass(name, b, cs); + } catch (Throwable th) { + trace.error("Weaving class problem. Original class has been returned. The error was caused because of: " + th, th); + clazz= super.defineClass(name, orig, cs); + } + if (trace.isTraceEnabled()) + trace.exit("defineClass", clazz); + return clazz; + } + + private void createAdaptor() { + DefaultWeavingContext weavingContext = new DefaultWeavingContext(this) { + + /* Ensures consistent LTW messages for testing */ + public String getClassLoaderName() { + ClassLoader loader = getClassLoader(); + return loader.getClass().getName(); + } + + }; + + ClassLoaderWeavingAdaptor clwAdaptor = new ClassLoaderWeavingAdaptor(); + initializingAdaptor = true; + clwAdaptor.initialize(this, weavingContext); + initializingAdaptor = false; + adaptor = clwAdaptor; + } + + /** + * Override to find classes generated by WeavingAdaptor + */ + protected byte[] getBytes(String name) throws IOException { + byte[] bytes = super.getBytes(name); + + if (bytes == null) { + // return adaptor.findClass(name); + return (byte[]) generatedClasses.remove(name); + } + + return bytes; + } + + /** + * Implement method from WeavingClassLoader + */ + public URL[] getAspectURLs() { + return aspectURLs; + } + + public void acceptClass (String name, byte[] classBytes, byte[] weavedBytes) { + generatedClasses.put(name, weavedBytes); + } + + // protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + // System.err.println("> WeavingURLClassLoader.loadClass() name=" + name); + // Class clazz= super.loadClass(name, resolve); + // System.err.println("< WeavingURLClassLoader.loadClass() clazz=" + clazz + ", loader=" + clazz.getClassLoader()); + // return clazz; + // } + + // private interface ClassPreProcessorAdaptor extends ClassPreProcessor { + // public void addURL(URL url); + // } + // + // private class WeavingAdaptorPreProcessor implements ClassPreProcessorAdaptor { + // + // private WeavingAdaptor adaptor; + // + // public WeavingAdaptorPreProcessor (WeavingClassLoader wcl) { + // adaptor = new WeavingAdaptor(wcl); + // } + // + // public void initialize() { + // } + // + // public byte[] preProcess(String className, byte[] bytes, ClassLoader classLoader) { + // return adaptor.weaveClass(className,bytes); + // } + // + // public void addURL(URL url) { + // + // } + // } + +} |