Search code examples
javajvminstrumentationjava-bytecode-asmjavaagents

When visiting a class using ASM, how to know the ancestor class of that class without loading any class?


What I want to do is: I have a ClassVisitor adapter, when entering the visit method, I want to know if the class AncestorClass is the ancestor class of that class? I have tried to use reflection (Class.forname(...)) like this:

MyTransformer.class:

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:

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

but the problem is that I also want to instrument the parent class of the instrumented class. I found that once Class.forName() loads the parent class, the parent class will not get into the transform method. Which means the parent class can not be instrumented any more. So is there any alternatives to find out whether the class is a descendant class of AncestorClass?


Solution

  • Thanks to @JohannesKuhn and @Holger, I finally found the way to analyze the inheritance relationship statically.

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