diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/custom/ScalaTraitFinalFieldTransformer.java b/newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/custom/ScalaTraitFinalFieldTransformer.java index 9c766c8390..6bbb27bb56 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/custom/ScalaTraitFinalFieldTransformer.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/custom/ScalaTraitFinalFieldTransformer.java @@ -3,6 +3,8 @@ import com.newrelic.agent.instrumentation.classmatchers.OptimizedClassMatcher; import com.newrelic.agent.instrumentation.context.ContextClassTransformer; import com.newrelic.agent.instrumentation.context.InstrumentationContext; +import com.newrelic.agent.util.asm.CustomClassLoaderClassWriter; +import com.newrelic.agent.util.asm.PatchedClassWriter; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; @@ -34,7 +36,7 @@ private byte[] doTransform(ClassLoader loader, String className, Class classB LOG.debug("Instrumenting class " + className); ClassReader reader = new ClassReader(classfileBuffer); - ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + ClassWriter writer = new CustomClassLoaderClassWriter(ClassWriter.COMPUTE_FRAMES, loader); ClassVisitor cv = writer; cv = new ScalaTraitFinalFieldVisitor(cv, context.getScalaFinalFields()); reader.accept(cv, ClassReader.SKIP_FRAMES); diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/util/asm/CustomClassLoaderClassWriter.java b/newrelic-agent/src/main/java/com/newrelic/agent/util/asm/CustomClassLoaderClassWriter.java new file mode 100644 index 0000000000..00e9c8bd34 --- /dev/null +++ b/newrelic-agent/src/main/java/com/newrelic/agent/util/asm/CustomClassLoaderClassWriter.java @@ -0,0 +1,85 @@ +package com.newrelic.agent.util.asm; + +import com.newrelic.agent.Agent; +import com.newrelic.agent.bridge.AgentBridge; +import org.objectweb.asm.ClassReader; + +import java.io.IOException; +import java.util.logging.Level; + +public class CustomClassLoaderClassWriter extends PatchedClassWriter { + + private final ClassLoader classLoader; + private final ClassResolver classResolver; + + public CustomClassLoaderClassWriter(int flags, ClassLoader classLoader) { + super(flags, classLoader); + this.classLoader = classLoader; + this.classResolver = ClassResolvers.getClassLoaderResolver(classLoader == null ? + AgentBridge.getAgent().getClass().getClassLoader() : classLoader); + } + + private Class loadClass(String type) throws ClassNotFoundException { + Class result = null; + try { + // try the custom classloader first + result = classLoader.loadClass(type); + } catch (ClassNotFoundException e) { + Agent.LOG.log(Level.FINEST, "class not found in custom classloader: "+type); + try { + // now try the base classloader + result = this.getClass().getClassLoader().loadClass(type); + } catch (ClassNotFoundException e2) { + Agent.LOG.log(Level.FINEST, "class not found in base classloader: "+type); + try { + // if all else fails, let's try the hard way + // this case exists because of continued TypeNotPresentExceptions when instrumenting Scala + ClassReader classReader = getClassReader(type); + if (classReader != null) { + result = classReader.getClass(); + } + } catch (IOException ioe) { + Agent.LOG.log(Level.FINEST, ioe.toString(), ioe); + throw new ClassNotFoundException("Could not find class via ClassReader: "+type); + } + } + } + + return result; + } + + protected String getCommonSuperClass(String type1, String type2) { + Class class1; + try { + class1 = loadClass(type1); + } catch (ClassNotFoundException e) { + throw new TypeNotPresentException(type1, e); + } + + Class class2; + try { + class2 = loadClass(type2); + } catch (ClassNotFoundException e) { + throw new TypeNotPresentException(type2, e); + } + + if (class1 == null || class2 == null) { + return JAVA_LANG_OBJECT; + } + + if (class1.isAssignableFrom(class2)) { + return type1; + } else if (class2.isAssignableFrom(class1)) { + return type2; + } else if (!class1.isInterface() && !class2.isInterface()) { + do { + class1 = class1.getSuperclass(); + } while(!class1.isAssignableFrom(class2)); + + return class1.getName().replace('.', '/'); + } else { + return "java/lang/Object"; + } + } + +} diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/util/asm/PatchedClassWriter.java b/newrelic-agent/src/main/java/com/newrelic/agent/util/asm/PatchedClassWriter.java index 12ed16329f..b78cf6799d 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/util/asm/PatchedClassWriter.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/util/asm/PatchedClassWriter.java @@ -29,7 +29,7 @@ * structure without actually loading any classes. */ public class PatchedClassWriter extends ClassWriter { - private static final String JAVA_LANG_OBJECT = "java/lang/Object"; + static final String JAVA_LANG_OBJECT = "java/lang/Object"; protected final ClassResolver classResolver; public PatchedClassWriter(int flags, ClassLoader classLoader) { diff --git a/newrelic-agent/src/test/java/com/newrelic/agent/util/asm/CustomClassLoaderClassWriterTest.java b/newrelic-agent/src/test/java/com/newrelic/agent/util/asm/CustomClassLoaderClassWriterTest.java new file mode 100644 index 0000000000..7c0857a4b3 --- /dev/null +++ b/newrelic-agent/src/test/java/com/newrelic/agent/util/asm/CustomClassLoaderClassWriterTest.java @@ -0,0 +1,55 @@ +package com.newrelic.agent.util.asm; + +import org.junit.Assert; +import org.junit.Test; +import org.objectweb.asm.ClassWriter; + +import java.io.Serializable; + +public class CustomClassLoaderClassWriterTest { + + CustomClassLoaderClassWriter api = new CustomClassLoaderClassWriter(ClassWriter.COMPUTE_FRAMES, this.getClass().getClassLoader()); + + @Test + public void test_getCommonSuperClass_sameClass() { + String className = CustomClassLoaderClassWriterTest.class.getName(); + Assert.assertEquals(className, api.getCommonSuperClass(className, className)); + } + + @Test + public void test_getCommonSuperClass_commonSuper() { + Assert.assertEquals(Object.class.getName().replace('.','/'), + api.getCommonSuperClass(String.class.getName(), Integer.class.getName())); + } + + @Test + public void test_getCommonSuperClass_commonSuper2() { + Assert.assertEquals(Number.class.getName(), + api.getCommonSuperClass(Integer.class.getName(), Number.class.getName())); + } + + @Test + public void test_getCommonSuperClass_interfaces() { + Assert.assertEquals(Object.class.getName().replace('.','/'), + api.getCommonSuperClass(Comparable.class.getName(), Serializable.class.getName())); + } + + @Test + public void test_getCommonSuperClass_noCommonSuper() { + Assert.assertEquals(Object.class.getName().replace('.','/'), + api.getCommonSuperClass(String.class.getName(), CustomClassLoaderClassWriterTest.class.getName())); + } + + @Test + public void test_getCommonSuperClass_invalidClass1() { + Assert.assertEquals(Object.class.getName().replace('.', '/'), + api.getCommonSuperClass("noexist1", CustomClassLoaderClassWriterTest.class.getName())); + } + + @Test + public void test_getCommonSuperClass_invalidClass2() { + Assert.assertEquals(Object.class.getName().replace('.', '/'), + api.getCommonSuperClass(CustomClassLoaderClassWriterTest.class.getName(), "noexist2")); + } + +}