I am trying to instrument dependencies using ASM 5.2. With the JDK 1.8 being mandated I am forced to use COMPUTE_FRAMES option in my class writer.
When using default ClassWriter it cannot find certain classes and throws the following exception
java.lang.RuntimeException: java.lang.ClassNotFoundException: XXX
at org.objectweb.asm.ClassWriter.getCommonSuperClass(Unknown Source)
at org.objectweb.asm.ClassWriter.a(Unknown Source)
at org.objectweb.asm.Frame.a(Unknown Source)
at org.objectweb.asm.Frame.a(Unknown Source)
at org.objectweb.asm.MethodWriter.visitMaxs(Unknown Source)
.....
In order to resolve this issue I override the getCommonSuperClass() method of the ClassWriter by creating a custom classWriter class and passing it the class loader from the transform() . I followed the advise from this thread: ASM 5.0.3 With Java 1.8 incorrect maxStack with Java.lang.VerifyError: Operand stack overflow
Here is the CustomClassWriter code:
class CustomClassWriter extends ClassWriter {
ClassLoader classLoader;
public CustomClassWriter(int writerFlag, ClassLoader loader)
{
super(writerFlag);
this.classLoader = loader;
System.out.println("Trace: Registering class loader from Code injector");
}
/**
* This method returns common super class for both the classes. If no super class
* is present it returns java/lang/Object class.
* @param className1
* @param className2
* @return The String for the common super class of both the classes
*/
protected String getCommonSuperClass(String className1, String className2)
{
Class class1;
Class class2;
//System.out.println("Trace: Default Class loader :" + getClass().getClassLoader().toString());
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
System.out.println("Trace: Context class loader is " + contextClassLoader.toString());
System.out.println("Trace: Loaded class loaded :" + classLoader.toString());
try
{
class1 = Class.forName(className1.replace('/', '.'), false, this.classLoader);
class2 = Class.forName(className2.replace('/', '.'), false, this.classLoader);
System.out.println("Trace: Class 1" + class1.toString());
System.out.println("Trace: Class 2" + class2.toString());
}
catch (Exception th) {
throw new RuntimeException(th.getMessage());
}
if (class1.isAssignableFrom(class2)) {
return className1;
}
if (class2.isAssignableFrom(class1)) {
return className2;
}
if ((class1.isInterface()) || (class2.isInterface())) {
return "java/lang/Object";
}
do {
class1 = class1.getSuperclass();
}
while (!(class1.isAssignableFrom(class2)));
return class1.getName().replace('.', '/');
}
}
However, after doing this I run into weiered issue when I try to use the java agent with Eureka Netflix library :
Caused by: java.lang.LinkageError: loader (instance of sun/misc/Launcher$AppClassLoader): attempted duplicate class definition for name: "org/apache/http/impl/client/AbstractHttpClient"
at java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.8.0_144]
at java.lang.ClassLoader.defineClass(ClassLoader.java:763) ~[na:1.8.0_144]
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) ~[na:1.8.0_144]
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467) ~[na:1.8.0_144]
at java.net.URLClassLoader.access$100(URLClassLoader.java:73) ~[na:1.8.0_144]
at java.net.URLClassLoader$1.run(URLClassLoader.java:368) ~[na:1.8.0_144]
at java.net.URLClassLoader$1.run(URLClassLoader.java:362) ~[na:1.8.0_144]
at java.security.AccessController.doPrivileged(Native Method) ~[na:1.8.0_144]
at java.net.URLClassLoader.findClass(URLClassLoader.java:361) ~[na:1.8.0_144]
at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_144]
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335) ~[na:1.8.0_144]
at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[na:1.8.0_144]
at java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.8.0_144]
at java.lang.ClassLoader.defineClass(ClassLoader.java:763) ~[na:1.8.0_144]
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) ~[na:1.8.0_144]
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467) ~[na:1.8.0_144]
at java.net.URLClassLoader.access$100(URLClassLoader.java:73) ~[na:1.8.0_144]
at java.net.URLClassLoader$1.run(URLClassLoader.java:368) ~[na:1.8.0_144]
at java.net.URLClassLoader$1.run(URLClassLoader.java:362) ~[na:1.8.0_144]
at java.security.AccessController.doPrivileged(Native Method) ~[na:1.8.0_144]
at java.net.URLClassLoader.findClass(URLClassLoader.java:361) ~[na:1.8.0_144]
at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_144]
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335) ~[na:1.8.0_144]
at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[na:1.8.0_144]
at com.sun.jersey.client.apache4.ApacheHttpClient4.createDefaultClientHandler(ApacheHttpClient4.java:236) ~[jersey-apache-client4-1.19.1.jar:1.19.1]
at com.sun.jersey.client.apache4.ApacheHttpClient4.create(ApacheHttpClient4.java:181) ~[jersey-apache-client4-1.19.1.jar:1.19.1]
at com.netflix.discovery.shared.transport.jersey.EurekaJerseyClientImpl.<init>(EurekaJerseyClientImpl.java:52) ~[eureka-client-1.4.12.jar:1.4.12]
... 58 common frames omitted
I am not sure what is the exact issue here. How does class loader gets different gets duplicate definitions. What could be the way to resolve this? Any help would be greatly appreciated.
Edit: In addition to the above context, I am already repackaging the http client dependencies into custom namespace so that the agent doesn't instrument our own calls.
The problem precisely arises when I try to instrument Apache HTTP Client. Currently I have instrumentations for Apache HTTP Client 4.2 and 4.3 and when I boot the application by removing the instrumentation for 4.2 everything works fine.
Here are the definitions I am trying to instrument:
private final static String HTTP_CLIENT_43_CLASS_NAME = "org/apache/http/impl/client/InternalHttpClient";
private final static String HTTP_CLIENT_METHOD_43_NAME = "doExecute";
private final static String HTTP_CLIENT_METHOD_43_SIGNATURE = "(Lorg/apache/http/HttpHost;Lorg/apache/http/HttpRequest;Lorg/apache/http/protocol/HttpContext;)Lorg/apache/http/client/methods/CloseableHttpResponse;";
private final static String HTTP_CLIENT_42_CLASS_NAME = "org/apache/http/impl/client/AbstractHttpClient";
private final static String HTTP_CLIENT_METHOD_42_NAME = "execute";
private final static String HTTP_CLIENT_METHOD_42_SIGNATURE = "(Lorg/apache/http/HttpHost;Lorg/apache/http/HttpRequest;Lorg/apache/http/protocol/HttpContext;)Lorg/apache/http/HttpResponse;";