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

StackMapFrames missing from classes to be retransformed by ClassFileTransformer

XMLWordPrintable

    • generic
    • generic

      A DESCRIPTION OF THE PROBLEM :
      When class retransformation is requested for an already loaded Java standard library class and a Java agent capable of retransformation is registered, the class file bytes received by the agent via the `classfileBytes` parameter of `ClassFileTransformer#transform` do not contain stack frames (that is, do not contain any StackMapTable attributes on methods).

      Since bytecode manipulation frameworks such as ASM rely on stack map table entries to calculate the number of stack entries and local variables, they fail to run on these classes or produce class files as output that fail at runtime with errors such as ""fatal error: Illegal class file encountered. Try running with -Xverify:all in method loadProvider".

      This bug is similar to https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8228604, but different in at least three ways:
      * It is reachable from the Java Instrumentation interface, without any direct use of the JVMTI native instrumentation capabilities.
      * It only seems to affect Java standard library classes.
      * It still affects JDK 19.

      As a

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      The reproducer is available at https://github.com/fmeum/retransform-stack-frames in source form or https://github.com/fmeum/retransform-stack-frames/releases/download/1.0.0/reproducing_agent.jar in compiled form.

      Either execute `./run.sh` (source form) or `java -javaagent:reproducing_agent.jar -jar reproducing_agent.jar` (compiled form) to execute.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Transforming java.lang.ProcessBuilder
      Has stack map frames: true
      Retransforming java.lang.ProcessBuilder
      Has stack map frames: true

      That is, when retransforming, the class file bytes still contain stack map frames.
      ACTUAL -
      Transforming java.lang.ProcessBuilder
      Has stack map frames: true
      Retransforming java.lang.ProcessBuilder
      Has stack map frames: false

      That is, when retransforming, the class file bytes do not contain stack map frames.

      ---------- BEGIN SOURCE ----------
      Source file (requires ASM as a dependency, full setup is available at https://github.com/fmeum/retransform-stack-frames):

      package org.example;

      import java.lang.instrument.ClassFileTransformer;
      import java.lang.instrument.Instrumentation;
      import java.lang.instrument.UnmodifiableClassException;
      import java.security.ProtectionDomain;
      import org.objectweb.asm.ClassReader;
      import org.objectweb.asm.ClassVisitor;
      import org.objectweb.asm.MethodVisitor;
      import org.objectweb.asm.Opcodes;

      public class Agent implements ClassFileTransformer {

        private boolean hasStackMapFrames(byte[] classfileBuffer) {
          ClassReader reader = new ClassReader(classfileBuffer);
          final boolean[] hasFrames = {false};
          ClassVisitor cv = new ClassVisitor(Opcodes.ASM9) {
            @Override
            public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
                String[] exceptions) {
              return new MethodVisitor(Opcodes.ASM9) {
                @Override
                public void visitFrame(int type, int numLocal, Object[] local, int numStack,
                    Object[] stack) {
                  hasFrames[0] = true;
                }
              };
            }
          };
          reader.accept(cv, 0);
          return hasFrames[0];
        }

        @Override
        public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
            ProtectionDomain protectionDomain, byte[] classfileBuffer) {
          if (!className.equals("java/lang/ProcessBuilder")) {
            return null;
          }
          System.err.printf("%s java.lang.ProcessBuilder%n", classBeingRedefined != null ? "Retransforming" : "Transforming");
          System.err.println("Has stack map frames: " + hasStackMapFrames(classfileBuffer));
          return null;
        }

        @Override
        public byte[] transform(Module module, ClassLoader loader, String className,
            Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
          return transform(loader, className, classBeingRedefined, protectionDomain, classfileBuffer);
        }

        public static void premain(String agentArgs, Instrumentation inst) {
          inst.addTransformer(new Agent(), true);
          try {
            inst.retransformClasses(ProcessBuilder.class);
          } catch (UnmodifiableClassException e) {
            e.printStackTrace();
          }
        }

        public static void main(String[] args) {
        }
      }
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Using a library such as ClassGraph to locate the original class file and use its bytes. This breaks ClassFileTransformer composition and introduces a lot of additional complexity.

      FREQUENCY : always


            amenkov Alex Menkov
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: