как создать экземпляр другого класса с помощью Instrumenting/ASM

Я пытаюсь использовать ASM в javaagent , чтобы изменить создаваемый класс (sun/misc/URLClassPath) в другой (fommil/URLClassPath), который наследуется от него и переопределяет все методы. Я знаю, что целевой класс (java/net/URLClassLoader), которым я являюсь retransforming, — единственное, что создает sun/misc/URLClassPaths и только в его конструкторе.

Основная идея примерно такая:

@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);
            }
        }
    };
}

Я могу поместить println в конструктор fommil/URLClassPath и посмотреть, как он создается!

Однако ни один из методов fommil.URLClassPath не вызывается. Всегда вызываются только методы суперкласса.

Даже если я изменю приведенный выше код так, чтобы не только invokespecial/<init>, но и все вызовы URLClassPath были переписаны так, чтобы их invokevirtual указывали на мой класс, мои методы все равно никогда не вызываются. Я даже пытался сделать это для всех внутренних классов URLClassLoader.

Так почему же invokevirtual не находят методы переопределения, даже если они повторно преобразованы? То, что я делаю — изменение типа конструируемой вещи — принципиально невозможно? Если да, то может кто-нибудь объяснить, почему?

Я знаю, что инструментирование основных классов JDK довольно зло, но, честно говоря, у меня нет большой альтернативы.

Единственное, что осталось попробовать, — это инструментировать все классы, которые пытаются создать экземпляр URLClassLoader, заставить их вставляться во внутреннее поле ucp и заменять его моей реализацией.


person fommil    schedule 16.04.2016    source источник
comment
Вы используете invokespecial, который предназначен для конструкторов (что правильно), а не invokevirtual, который предназначен для методов, которые можно переопределить.   -  person Peter Lawrey    schedule 16.04.2016
comment
@PeterLawrey прочитайте код еще раз, я понимаю, что вы говорите. Это не проблема.   -  person fommil    schedule 16.04.2016
comment
Я не говорил, что это было, поэтому я прокомментировал. Я подозреваю, что URLCLassLoader вызывается до того, как ваше преобразование может начать работу. Что-то должно загрузить классы, которые использует ваш преобразователь.   -  person Peter Lawrey    schedule 16.04.2016
comment
это ретрансляция. Обратите внимание: я могу поместить println в конструктор fommil/URLClassPath и увидеть, как он создается!   -  person fommil    schedule 16.04.2016
comment
Он строится во всех местах, где вам нужно его использовать. например скажем, ваша JVM создает URLClassLoader для загрузки всех основных классов и вашего преобразователя. После изменения части кода ваш URLClassLoader изменит тип или будет продолжать использоваться?   -  person Peter Lawrey    schedule 16.04.2016
comment
URLClassLoader, который я явно тестирую, создается ПОСЛЕ инструментария. Но вы хорошо заметили, что к этому моменту в памяти уже могут быть URLClassLoaders.   -  person fommil    schedule 16.04.2016
comment
Пока вы не используете его, его все еще можно использовать. Рассматривали ли вы инструментирование класса URLClassLaoder, чтобы он делал то, что вы хотите?   -  person Peter Lawrey    schedule 16.04.2016
comment
Давайте продолжим обсуждение в чате.   -  person fommil    schedule 16.04.2016


Ответы (1)


Я понял!

Рассмотрим этот простой код

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

который производит

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

в сравнении с

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

который производит

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

Обратите внимание на ключевое слово new, в котором есть тип!

Проблема в том, что если кто-то хочет изменить конструктор, он должен также изменить возвращаемое определение типа. то есть добавить это

        @Override
        public void visitTypeInsn(int opcode, String type) {
            if ("sun/misc/URLClassPath".equals(type))
                super.visitTypeInsn(opcode, "fommil/URLClassPath");
            else
                super.visitTypeInsn(opcode, type);
        }
person fommil    schedule 16.04.2016