/******************************************************************************* * Copyright (c) 2005 Contributors. * All rights reserved. * This program and the accompanying materials are made available * under the terms of the Eclipse Public License v 2.0 * which accompanies this distribution and is available at * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt * * Contributors: * Alexandre Vasseur initial implementation *******************************************************************************/ package org.aspectj.weaver.loadtime; import java.lang.ref.Reference; 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.List; import java.util.Map; import java.util.Optional; 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 Alexandre Vasseur (alex AT gnilux DOT com) */ 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("", this, new Object[] { context, getClass().getClassLoader() }); this.weavingContext = context; if (trace.isTraceEnabled()) trace.exit(""); } /** * 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, final byte[] bytes, ClassLoader classLoader, ProtectionDomain protectionDomain) { // Implementation note: Try to return null, whenever it is clear that the original byte code was not changed by the // weaver. While not strictly necessary, the changes from commit 3e3c83712e are defensive programming, which helps // to avoid problems like https://bugs.openjdk.org/browse/JDK-8325536, even if unfixed in the JDK. if (classLoader == null || className == null || classLoader.getClass().getName().equals(deleLoader) || classLoader.getClass().getName().equals(deleLoader2)) { // skip boot loader, null classes (hibernate), or those from a reflection loader return null; } if (loadersToSkip != null) { // Check whether to reject it if (loadersToSkip.contains(classLoader.getClass().getName())) { // System.out.println("debug: no weaver created for loader '"+loader.getClass().getName()+"'"); return null; } } if (trace.isTraceEnabled()) trace.enter("preProcess", this, new Object[] { className, bytes, classLoader }); if (trace.isTraceEnabled()) trace.event("preProcess", this, new Object[] { classLoader.getParent(), Thread.currentThread().getContextClassLoader() }); try { synchronized (classLoader) { if (SimpleCacheFactory.isEnabled()) { Optional cacheBytes = laCache.getAndInitialize(className, bytes, classLoader, protectionDomain); if (cacheBytes != null){ return cacheBytes.orElse(null); } } WeavingAdaptor weavingAdaptor = WeaverContainer.getWeaver(classLoader, weavingContext); if (weavingAdaptor == null) { if (trace.isTraceEnabled()) trace.exit("preProcess"); return null; } 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 null; } 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 m = WeaverContainer.weavingAdaptors; Set keys = m.keySet(); for (AdaptorKey object : keys) { System.err.println(object + " = " + WeaverContainer.weavingAdaptors.get(object)); } } Reference 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 m = WeaverContainer.weavingAdaptors; Set keys = m.keySet(); for (AdaptorKey object : keys) { 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) { Reference 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 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<>(); } 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 weavingAdaptors = Collections.synchronizedMap(new HashMap<>()); 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); } }