I'm using the asm library to perform some Java bytecode modification - specifically to modify my classes to implement a new interface and associated methods. My current approach is using the core asm API via a javaagent. I'd like to keep this dynamic approach as opposed to statically modifying .class files.
At a higher level, my problem is that if I choose to modify class A, which extends from B, I also need to modify B. (Given my understanding of how classes are loaded in the JVM, I believe that class B will always be handed to a transformer before class A. (Please correct me if I'm wrong). Given that assumption, I'm thinking that I then need to go back and retransform B. My approach is captured in this bit of code:
public byte[] transform(ClassLoader l, String name, Class<?> clazz, ProtectionDomain d, byte[] b) {
throws IllegalClassFormatException {
// **1**
System.out.println("--->>> " + name);
if (interestingClass(name)) {
try {
ClassReader cr = new ClassReader(b);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
PyClassVisitorAdapter pv = new PyClassVisitorAdapter(cw, name);
cr.accept(pv, 0);
// **2** Retrieve the superclass and try to transform that
if (! "Ljava/lang/Object;".equals(pv.getSuperName())) {
String cName = classJvmToCanonical(pv.getSuperName());
Class[] classes = inst.getAllLoadedClasses();
for (Class c : classes) {
if (c.getName().equals(cName)) {
inst.retransformClasses(c);
break;
}
}
}
// Dump the transformed class
ClassReader cr2 = new ClassReader(cw.toByteArray());
ClassWriter cw2 = new ClassWriter(cr2, 0);
TraceClassVisitor tcv = new TraceClassVisitor(cw2, new PrintWriter(System.out));
cr2.accept(tcv, 0);
return cw2.toByteArray();
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
} else {
return b;
}
}
(inst
is a handle to Instrumentation
which gets passed in in the constructor)
The part I'm having a hard time with is the block marked in the comments with **2**
. Let's say again that A extends B and I'm 'interested' in transforming A. What I'm expecting is that I would see the name of the superclass (B) being printed at **1**
(but not getting transformed because I don't think it's interesting on the first pass) and then, once I get to **2**
and discover that A's superclass is B, I should be trying to retransform B. At this point I'm expecting this method to be called again (via inst.retransformClasses()
) and that I would see B getting printed at **1**
. However, I don't. (I have added print statements and am sure I'm reaching the retransform call. I've also checked that Instrumentation.isRetransformClassesSupported()
and Instrumentation.isModifiableClass(c)
both return true).
I believe I've set up the agent correctly; setting both Can-Retransform-Classes and Can-Redefine-Classes to true in the manifest. Also, when I add the transformer to the Instrumentation in the agent's premain
method I do this:
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new PyClassFileTransformer(inst), true);
}
Any insights as to what I'm doing wrong here? Thanks.
You could change your bytecode instrumentation strategy, so when class B is loaded, you find all its subclasses and decide at that point if you need to modify class B now. This can be optimized by maintaining class-metadata repository or cache in memory (i.e. information about class hierarchy), so you won't have to load metadata every time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With