0

I am trying to use ASM in a javaagent to change the class that is being constructed (sun/misc/URLClassPath) to another one (fommil/URLClassPath) that inherits from it and overrides all the methods. I know that the target class (java/net/URLClassLoader), which I am retransforming, is the only thing that creates sun/misc/URLClassPaths and only in its constructor.

The basic idea is something like this:

@Override
public MethodVisitor visitMethod(int access,
                                 String name,
                                 String desc,
                                 String signature,
                                 String[] exceptions) {
    MethodVisitor visitor = super.visitMethod(access, name, desc, signature, exceptions);

    return new MethodVisitor(Opcodes.ASM5, visitor) {
        @Override
        public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
            if (opcode == Opcodes.INVOKESPECIAL && "sun/misc/URLClassPath".equals(owner) && "<init>".equals(name)) {
                super.visitMethodInsn(opcode, "fommil/URLClassPath", name, desc, itf);
            } else {
                super.visitMethodInsn(opcode, owner, name, desc, itf);
            }
        }
    };
}

I can put a println in fommil/URLClassPath's constructor and see it being constructed!

However, none of fommil.URLClassPath's methods are being called. Only the methods on the super class are ever called.

Even if I change the above code so that not only invokespecial / <init>, but all calls to URLClassPath are rewritten so that their invokevirtual points to my class, my methods are still never called. I've even tried doing this for all of the inner classes of URLClassLoader.

So why are the invokevirtuals not finding the override methods, even when they are retransformed? Is what I'm doing - changing the type of the thing being constructed - just fundamentally not possible? If so, can somebody please explain why?

I am aware that instrumenting core JDK classes is pretty evil, but frankly I don't really have much of an alternative.

The only thing left to try is to instrument all classes that try to instantiate URLClassLoader and have them poke into the internal ucp field and replace it with my implementation.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
fommil
  • 5,757
  • 8
  • 41
  • 81
  • You are using invokespecial which is for constructors (which is correct) not invokevirtual which is for methods which could be overridden. – Peter Lawrey Apr 16 '16 at 17:59
  • @PeterLawrey read the code again, I'm aware of what you're saying. That's not the problem. – fommil Apr 16 '16 at 18:00
  • I didn't say it was which is why I commented. I suspect the URLCLassLoader is being called before your transformation can kick in. Something has to load the classes your transformer is using. – Peter Lawrey Apr 16 '16 at 18:02
  • this is a retransform. Note the bit: "I can put a println in fommil/URLClassPath's constructor and see it being constructed!" – fommil Apr 16 '16 at 18:03
  • Is it being constructed in all places you need it to be used. e.g. say your JVM creates a URLClassLoader to load all the basic classes and your transformer. After some of the code has been changed, will your URLClassLoader change type, or will it continue to be used? – Peter Lawrey Apr 16 '16 at 18:08
  • The URLClassLoader that I'm explicitly testing is constructed AFTER the instrumentation. But you make a good point that there may be URLClassLoaders already in memory by this point. – fommil Apr 16 '16 at 18:09
  • While you don't use it, it could still be used. Have you considered Instrumenting the URLClassLaoder class to do what you want? – Peter Lawrey Apr 16 '16 at 18:10
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/109336/discussion-between-fommil-and-peter-lawrey). – fommil Apr 16 '16 at 18:19

1 Answers1

1

I got it!

Consider this simple code

import java.util.*;    
class Baz {
    public void baz() {
        List list = new ArrayList();
    }
}

which produces

   0: new           #2                  // class java/util/ArrayList
   3: dup
   4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
   7: astore_1
   8: return

compared to

import java.util.*;    
class Baz {
    public void baz() {
        List list = new LinkedList();
    }
}

which produces

   0: new           #2                  // class java/util/LinkedList
   3: dup
   4: invokespecial #3                  // Method java/util/LinkedList."<init>":()V
   7: astore_1
   8: return

Note the new keyword which has a type in it!

The problem is that if one wishes to change the constructor one must also change the type definition that is returned. i.e. add this

        @Override
        public void visitTypeInsn(int opcode, String type) {
            if ("sun/misc/URLClassPath".equals(type))
                super.visitTypeInsn(opcode, "fommil/URLClassPath");
            else
                super.visitTypeInsn(opcode, type);
        }
fommil
  • 5,757
  • 8
  • 41
  • 81