Как при посещении класса с использованием ASM узнать класс-предок этого класса, не загружая какой-либо класс?

Что я хочу сделать, так это: у меня есть адаптер ClassVisitor, при входе в метод visit я хочу знать, является ли класс AncestorClass классом-предком этого класса? Я пытался использовать отражение (Class.forname(...)) следующим образом:

МойТрансформер.класс:

public class MyTransformer implements ClassFileTransformer {

    public byte[] transform(ClassLoader loader, String className,
                            Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) throws IllegalClassFormatException {

        if (className == null){
            return classfileBuffer;
        }
        ClassReader cr = new ClassReader(classfileBuffer);
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
        CollectionCVAdapter mca = new MyCVAdapter(cw, className);
        cr.accept(mca, 0);
        return cw.toByteArray();
    }
}

MyCVAdapter.класс:

class MyCVAdapter extends ClassVisitor {

    private String className;
    private boolean isDescendantClass;

    CollectionCVAdapter(ClassVisitor classVisitor, String className){
        super(Opcodes.ASM5, classVisitor);
        this.className = className;
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        // check if this class is a descendant class of AncestorClass.class
        try{
            Class superClass = Class.forName(superName.replace("/", "."));
            do {
                if (superClass == AncestorClass.class) {
                    this.isDescendantClass= true;
                    break;
                }
                superClass = superClass.getSuperclass();
            } while (superClass != null);
        }catch (Exception e){
            e.printStackTrace();
        }
        super.visit(version, access, name, signature, superName, interfaces);
    }
    ...
}

но проблема в том, что я также хочу инструментировать родительский класс инструментированного класса. Я обнаружил, что как только Class.forName() загрузит родительский класс, родительский класс не попадет в метод transform. Это означает, что родительский класс больше не может быть инструментирован. Итак, есть ли альтернативы, чтобы узнать, является ли класс потомком класса AncestorClass?


person Instein    schedule 26.07.2020    source источник
comment
Во-первых: используйте ClassReader.getSuperName(). Второе: загрузите файлы классов с помощью loader.getResourceAsStream(superName + ".class"). Разберите это с помощью ClassReader.   -  person Johannes Kuhn    schedule 26.07.2020
comment
В классической ClassLoader архитектуре нет пуленепробиваемого подхода. getResourceAsStream(internalName + ".class") - это лучший способ получить байт-код без загрузки класса, если конкретная реализация загрузчика классов последовательно предоставляет содержимое файлов классов в качестве ресурса (все стандартные загрузчики делают). С модульным кодом код всегда загружается стандартным загрузчиком, делегирующим ModuleReader для получения байт-кода, поэтому getResourceAsStream всегда будет соответствовать loadClass в пределах одного и того же ModuleLayer.   -  person Holger    schedule 27.07.2020
comment
@Holger Не могли бы вы рассказать больше о ModuleReader? Я не слышал этого раньше. В методе transform я могу получить аргумент ClassLoader, чтобы использовать getResourceAsStream. Но в других случаях, когда у меня нет доступного ClassLoader и я хочу использовать getResourceAsStream для получения байт-кода, я обычно использую Class.forName(className).getClassLoader().getResourceAsStream. На самом деле, я не уверен, какой загрузчик может дать мне нужный байт-код. Есть ли более изящные способы?   -  person Instein    schedule 27.07.2020
comment
@JohannesKuhn Спасибо!   -  person Instein    schedule 27.07.2020
comment
Поскольку вы реализуете ClassFileTransformer, вы должны использовать ClassLoader, который был предоставлен в качестве первого аргумента для метода transform. Если это null, используйте ClassLoader.getSystemResourceAsStream(…).   -  person Holger    schedule 27.07.2020
comment
Но если базовый класс, который вы проверяете, не является системным классом, вы можете запретить системным классам быть его подклассом, поэтому вы можете просто пропустить, когда аргумент загрузчика класса равен null. Точно так же вы можете использовать ярлык всякий раз, когда имя класса начинается с java/.   -  person Holger    schedule 27.07.2020


Ответы (1)


Благодаря @JohannesKuhn и @Holger я наконец нашел способ статического анализа отношений наследования.

    @Override
    public void visit(int version, int access, String name, String signature, String superSlashName, String[] interfaces) {
        String originalSuperName = superSlashName;
        // check if this class is a subclass of AncestorClass.class
        try{
            while (superSlashName != null){
                if (superSlashName.equals(AncestorClassName)){
                    this.isDescendantClass= true;
                    break;
                }else{
                    InputStream is = this.loader.getResourceAsStream(superSlashName + ".class");
                    byte[] superBytes = FileUtil.loadByteCode(is);
                    ClassReader parentCr = new ClassReader(superBytes);
                    superSlashName = parentCr.getSuperName();
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        super.visit(version, access, name, signature, originalSuperName, interfaces);
    }
person Instein    schedule 27.07.2020