org.aspectj.weaver.loadtime.Aj Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aspectjtools Show documentation
Show all versions of aspectjtools Show documentation
Tools from the AspectJ project
/*******************************************************************************
* 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);
}
}