Jens
Jens

Reputation: 131

How do I use Instrumentation.retransformClasses() correctly from within asm code?

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.

Upvotes: 8

Views: 1856

Answers (1)

Eugene Kuleshov
Eugene Kuleshov

Reputation: 31795

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.

Upvotes: 1

Related Questions