1

I am trying to load classes from an agent implemented using ByteBuddy. I have a class defined in the agent and want to load it in the target program. here is what my transformer looks like:

public class ClassLoaderTransformer implements AgentBuilder.Transformer {
    private final Class<?> targetClass;

    public ClassLoaderTransformer(Class<?> targetClass) {
        this.targetClass = targetClass;
    }

    @Override
    public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
        try {
            final Class<?> aClass = Class.forName(typeDescription.getName());

            resolveClassLoadingStrategy(aClass).load(classLoader, singletonMap(
                    new TypeDescription.ForLoadedType(targetClass),
                    ClassFileLocator.ForClassLoader.read(targetClass)
            ));
        } catch (Exception e){
            System.out.println("Something went terribly wrong: " + e.getMessage());
        }

        return builder;
    }

    private static ClassLoadingStrategy<ClassLoader> resolveClassLoadingStrategy(Class<?> targetClass) throws IllegalAccessException {
        if ( !ClassInjector.UsingLookup.isAvailable() ) {
            return new ClassLoadingStrategy.ForUnsafeInjection(targetClass.getProtectionDomain() );
        }

        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodHandles.Lookup privateLookup = MethodHandles.privateLookupIn(targetClass, lookup);

        return ClassLoadingStrategy.UsingLookup.of( privateLookup );
    }
}

However, this code is not working. It throws the following error:

tech.ikora.seleniumagent.helpers.SourcePageFetcher must be defined in the same package as org.openqa.selenium.remote.RemoteWebDriver

and the stack traces looks like this:

java.lang.IllegalArgumentException: tech.ikora.seleniumagent.helpers.SourcePageFetcher must be defined in the same package as org.openqa.selenium.remote.RemoteWebDriver
        at net.bytebuddy.dynamic.loading.ClassInjector$UsingLookup.injectRaw(ClassInjector.java:1414)
        at net.bytebuddy.dynamic.loading.ClassInjector$AbstractBase.inject(ClassInjector.java:110)
        at net.bytebuddy.dynamic.loading.ClassLoadingStrategy$UsingLookup.load(ClassLoadingStrategy.java:492)
        at tech.ikora.seleniumagent.ClassLoaderTransformer.transform(ClassLoaderTransformer.java:27)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.doTransform(AgentBuilder.java:10364)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:10302)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.access$1600(AgentBuilder.java:10068)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$Java9CapableVmDispatcher.run(AgentBuilder.java:10761)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$Java9CapableVmDispatcher.run(AgentBuilder.java:10699)
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:389)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:10258)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport.transform(Unknown Source)
        at java.instrument/sun.instrument.TransformerManager.transform(TransformerManager.java:188)
        at java.instrument/sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:563)
        at java.base/java.lang.ClassLoader.defineClass1(Native Method)
        at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
        at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:151)
        at java.base/java.net.URLClassLoader.defineClass(URLClassLoader.java:515)
        at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:423)
        at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:417)
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:689)
        at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:416)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:588)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
        at org.apache.maven.surefire.booter.IsolatedClassLoader.loadClass(IsolatedClassLoader.java:97)
        at java.base/java.lang.ClassLoader.defineClass1(Native Method)
        at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
        at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:151)
        at java.base/java.net.URLClassLoader.defineClass(URLClassLoader.java:515)
        at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:423)
        at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:417)
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:689)
        at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:416)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:588)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
        at org.apache.maven.surefire.booter.IsolatedClassLoader.loadClass(IsolatedClassLoader.java:97)
        at base.BaseTest.setup(BaseTest.java:31)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:567)
        at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:132)
        at org.testng.internal.MethodInvocationHelper.invokeMethodConsideringTimeout(MethodInvocationHelper.java:61)
        at org.testng.internal.ConfigInvoker.invokeConfigurationMethod(ConfigInvoker.java:366)
        at org.testng.internal.ConfigInvoker.invokeConfigurations(ConfigInvoker.java:320)
        at org.testng.internal.TestMethodWorker.invokeBeforeClassMethods(TestMethodWorker.java:176)
        at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:122)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
        at org.testng.TestRunner.privateRun(TestRunner.java:764)
        at org.testng.TestRunner.run(TestRunner.java:585)
        at org.testng.SuiteRunner.runTest(SuiteRunner.java:384)
        at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:378)
        at org.testng.SuiteRunner.privateRun(SuiteRunner.java:337)
        at org.testng.SuiteRunner.run(SuiteRunner.java:286)
        at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:53)
        at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:96)
        at org.testng.TestNG.runSuitesSequentially(TestNG.java:1218)
        at org.testng.TestNG.runSuitesLocally(TestNG.java:1140)
        at org.testng.TestNG.runSuites(TestNG.java:1069)
        at org.testng.TestNG.run(TestNG.java:1037)
        at org.apache.maven.surefire.testng.TestNGExecutor.run(TestNGExecutor.java:77)
        at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.executeMulti(TestNGDirectoryTestSuite.java:159)
        at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.execute(TestNGDirectoryTestSuite.java:99)
        at org.apache.maven.surefire.testng.TestNGProvider.invoke(TestNGProvider.java:106)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:567)
        at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
        at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
        at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
        at org.apache.maven.plugin.surefire.InPluginVMSurefireStarter.runSuitesInProcess(InPluginVMSurefireStarter.java:80)
        at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeProvider(AbstractSurefireMojo.java:724)
        at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeAllProviders(AbstractSurefireMojo.java:682)
        at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeAfterPreconditionsChecked(AbstractSurefireMojo.java:648)
        at org.apache.maven.plugin.surefire.AbstractSurefireMojo.execute(AbstractSurefireMojo.java:586)
        at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:137)
        at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:210)
        at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:156)
        at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:148)
        at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:117)
        at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:81)
        at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build(SingleThreadedBuilder.java:56)
        at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:128)
        at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:305)
        at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:192)
        at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:105)
        at org.apache.maven.cli.MavenCli.execute(MavenCli.java:957)
        at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:289)
        at org.apache.maven.cli.MavenCli.main(MavenCli.java:193)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:567)
        at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:282)
        at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:225)
        at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:406)
        at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:347)

In this example, SourcePageFetcher is the targetClass and RemoveWebDriver is the class provided by the TypeDescription.

The goal of this class loader is to load classes that would be helpers to some of the Advice that I created to instrument my code. Should I try to use the ClassInjector in another way? Or am I seeing the problem in the wrong way or should I just use another ClassLoadingStrategy?

kabinja
  • 135
  • 2
  • 10
  • Please add the complete stack trace. The message looks suspiciously like it comes from an `InvocationTargetException` - whose cause is important here. – Johannes Kuhn Aug 18 '20 at 20:20
  • @JohannesKuhn Thanks for the comment, I edited my question to include the stack trace. It did indeed give additional information, clearly showing that I have an issue with loading the class more than once. – kabinja Aug 19 '20 at 07:26

2 Answers2

2

Following @RafaelWinterhalter suggestion, The solution that worked for me was to use the ClassInjector with UsingUnsafe. This lead to the following working code:

public class ClassLoaderTransformer implements AgentBuilder.Transformer {
    private final Class<?> targetClass;

    public ClassLoaderTransformer(Class<?> targetClass) {
        this.targetClass = targetClass;
    }

    @Override
    public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
        try {
            ClassInjector.UsingUnsafe.ofBootLoader().inject(singletonMap(
                    new TypeDescription.ForLoadedType(targetClass),
                    ClassFileLocator.ForClassLoader.read(targetClass)
            ));
        } catch (Throwable e){
            System.out.println("Something went terribly wrong: " + e.getMessage());
        }

        return builder;
    }
}

Note that in this case, the class is loading in the boot class loader, hence we should try to keep the classes loaded there as simple as possible. Furthermore, the classes loaded here seem to not have access to classes loaded by the children class loader, so again, I had to keep the classes to the bear minimal.

kabinja
  • 135
  • 2
  • 10
1

As the error message suggests: a lookup only allows you to define classes in packages of the specified lookup's class. If the package differs as it does for you:

tech.ikora.seleniumagent.helpers 
org.openqa.selenium.remote

the JVM would not allow for it. From an agent, you can however also use the UsingUnsafe strategy as the Instrumentation instance can grant you access to the JVM internals. Otherwise, you'd need to hook your injection to a class in the right package.

Note that you can rather use the ClassInjector directly instead of using a ClassLoadingStrategy.

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
  • Thanks a lot for the answer. I supposed it was that. I tried to change the name of the package in my agent, but it didn't make it work. So now as per your comment, I used the UsingUsage: `ClassInjector.UsingUnsafe.ofPlatformLoader().inject(singletonMap( new TypeDescription.ForLoadedType(targetClass), ClassFileLocator.ForClassLoader.read( targetClass)));`. But as soon as I hit the function call in my Advice, some kind of exception is raised and unfortunately, I didn't manage to capture it with a try catch on Exception. I am still unsure of what is the problem. Any suggestion? Thanks – kabinja Aug 20 '20 at 11:45
  • You cannot change the package of an instrumented class within a class file transformer. What is the exception you are getting? – Rafael Winterhalter Aug 20 '20 at 15:26
  • There is the `java.lang.NoClassDefFoundError: tech/ikora/seleniumagent/helpers/AgentSourcePageFetcher` that is thrown. It is like I am failing to actually load the class properly. But it was my understanding that loading in the System or bootloader would make the class accessible from all other classes. I also tried to call it using reflection, because I wasn't sure that the Advice would have access to the import statement, but it didn't help. Any idea what I might be doing wrong? Thanks again for your help, it is greatly appreciated! – kabinja Aug 20 '20 at 17:05
  • So playing a bit, I manage to make it work, the main change that i can think of is the fact that i called ofBootLoader instead of ofPlatformLoader when calling the ClassInjector. I will validate your answer and add another one with the code working. Thanks again for your help – kabinja Aug 21 '20 at 12:25