Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8202408

JVM fails to verify superinterface from agent transformed class on first load

XMLWordPrintable

    • x86_64
    • generic

      ADDITIONAL SYSTEM INFORMATION :
      All systems and OS, same issue with JDK 9 and 10

      A DESCRIPTION OF THE PROBLEM :
      JVM invokes `jdk.internal.module.Modules.transformedByAgent` when a given module (a class from the module) has been transformed by the agent such that the module can call into code provided by the boot class path or application class path. Unfortunately if the agent attempts to modify a class by adding additional interfaces (from the agent, hence unnamed module), then it triggers `IllegalAccessError` as below:

      It appears that the superinterface "verification" is executed before `jdk.internal.module.Modules.transformedByAgent` is invoked.

      Such an assumption is supported by the fact that if there's another other class that trigger transformation on the same module without extra interface (hence successfully trigger `transformedByAgent` on that module) , then all subsequent transformations on the same module would work, even with reference to interface from unnamed module from the agent.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1. Create an agent that transforms a class from module java.base, for example `java.net.HttpURLConnection`
      2. Use bytecode library to modify the class to implements an extra interface that is provided by the agent
      3. Package the agent
      4. Create a test driver that references `HttpURLConnection`
      5. Starts the test driver with javaagent argument pointing to the packaged agent
      6. `IllegalAccessError` is triggered



      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      JVM should be able to load the `HttpURLConnection` with the interface provided by the agent
      ACTUAL -
      Exception as below is thrown
      Exception in thread "main" java.lang.IllegalAccessError: superinterface check failed: class java.net.HttpURLConnection (in module java.base) cannot access class agent.MyInterface (in unnamed module @0x77b52d12) because module java.base does not read unnamed module @0x77b52d12
      at java.base/sun.net.www.protocol.http.Handler.openConnection(Handler.java:62)
      at java.base/sun.net.www.protocol.http.Handler.openConnection(Handler.java:57)
      at java.base/java.net.URL.openConnection(URL.java:1051)
      at TestDriver.main(TestDriver.java:16)

      ---------- BEGIN SOURCE ----------
      ==================================
      Agent.java
      ==================================

      package agent;

      import java.io.IOException;
      import java.lang.instrument.ClassFileTransformer;
      import java.lang.instrument.IllegalClassFormatException;
      import java.lang.instrument.Instrumentation;
      import java.security.ProtectionDomain;

      import javassist.CannotCompileException;
      import javassist.ClassPool;
      import javassist.CtClass;
      import javassist.LoaderClassPath;
      import javassist.NotFoundException;

      public class Agent {
          public static void premain(String args, Instrumentation instrumentation){
              instrumentation.addTransformer(new ClassFileTransformer() {
                  private ClassPool classPool;
                  {
                      classPool = new ClassPool();
                      if (ClassLoader.getSystemClassLoader() != null) {
                          //system class loader should have access to bootstrap class loader, which is where we make our agent classes available via MANIFEST.MF's Boot-Class-Path
                          classPool.appendClassPath(new LoaderClassPath(ClassLoader.getSystemClassLoader()));
                      }
                  }
                  
                  @Override
                  public byte[] transform(ClassLoader classLoader, String className, Class<?> inputClass, ProtectionDomain protectionDomain, byte[] classBytes) throws IllegalClassFormatException {
                      if (className.equals("java/util/concurrent/ThreadPoolExecutor")) { //doesn't have to be ThreadPoolExecutor, can be any class from module java.base, if this is triggered before `HttpURLConnection` transformation, then no longer trigger `IllegalAccessError`
                          System.out.println("Touching ThreadPoolExecutor");
                          return classBytes;//do nothing, but return non-null will convince JVM that it's been touched
                      }
                      
                      if (className.equals("java/net/HttpURLConnection")) {
                          try {
                              CtClass ctClass = classPool.makeClass(new java.io.ByteArrayInputStream(classBytes));
                              ctClass.addInterface(classPool.get(MyInterface.class.getName())); //tag an interface to this class
                              
                              System.out.println("Tagging interface to HttpURLConnection");
                              return ctClass.toBytecode();
                          } catch (NotFoundException | IOException | CannotCompileException e) {
                              e.printStackTrace();
                              return null;
                          }
                      }
                      return null;
                  }
              });
         }
          
         public interface MyInterface {}
      }

      ==================================
      agent's MANIFEST.MF
      ==================================
      Manifest-Version: 1.0
      Premain-Class: agent.Agent
      Can-Redefine-Classes: true
      Can-Retransform-Classes: true
      Boot-Class-Path: agent-module-issue-0.0.1-SNAPSHOT-jar-with-dependencies.jar
      Can-Set-Native-Method-Prefix: true

      ==================================
      agent's pom.xml
      ==================================
      <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>agent-module-issue</groupId>
        <artifactId>agent-module-issue</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <build>
          <sourceDirectory>src</sourceDirectory>
          <plugins>
            <plugin>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>3.7.0</version>
              <configuration>
                <source>1.7</source>
                <target>1.7</target>
              </configuration>
            </plugin>
            <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-jar-plugin</artifactId>
              <version>2.4</version>
              <configuration>
                <archive>
                  <manifestFile>${basedir}/resources/META-INF/MANIFEST.MF</manifestFile>
                </archive>
              </configuration>
            </plugin>
            <!-- Maven Assembly Plugin -->
            <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-assembly-plugin</artifactId>
              <version>2.4.1</version>
              <configuration>
                <!-- get all project dependencies -->
                <descriptorRefs>
                  <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <archive>
                  <manifestFile>${basedir}/resources/META-INF/MANIFEST.MF</manifestFile>
                </archive>
              </configuration>
              <executions>
                <execution>
                  <id>make-assembly</id>
                  <phase>package</phase>
                  <goals>
                    <goal>single</goal>
                  </goals>
                </execution>
              </executions>
            </plugin>
          </plugins>
        </build>
        <dependencies>
          <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.22.0-GA</version>
          </dependency>
        </dependencies>
      </project>

      ==================================
      TestDriver.java
      ==================================

      import java.io.IOException;
      import java.net.URL;
      import java.util.concurrent.ThreadPoolExecutor;

      public class TestDriver {
          public static void main(String[] args) throws InterruptedException, ClassNotFoundException, Exception, IOException {
      // Class<?> dummy = ThreadPoolExecutor.class; //if this is uncommented, then exception is no longer thrown
              new URL("http://www.google.com").openConnection().connect();
          }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      If the above TestDriver.java code has the `Class<?> dummy = ThreadPoolExecutor.class;` uncommented, then the classloading would work fine for `HttpURLConnection`

      FREQUENCY : always


            sspitsyn Serguei Spitsyn
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated: