-
Bug
-
Resolution: Duplicate
-
P4
-
None
-
19
-
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
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
- duplicates
-
JDK-8228604 StackMapFrames are missing from redefined class bytes of retransformed classes
-
- Resolved
-