Search code examples
javajava-bytecode-asm

Java bytecode instrumentation using ASM, MethodVisitor is null


So, with the code written below, my output is:

Starting application with the Agent
Visiting class: HelloWorld
Class Major Version: 51
Super class: java/lang/Object
Source: HelloWorld.java
Method: <init> desc = ()V cv = com.amir.agent.instrumentor.amirClassVisitor$1@79f1d448 and mv = null
Method: main desc = ([Ljava/lang/String;)V cv = com.amir.agent.instrumentor.amirClassVisitor$1@79f1d448 and mv = null
Method: foo desc = ()V cv = com.amir.agent.instrumentor.amirClassVisitor$1@79f1d448 and mv = null
Method ends here
Done instrumenting: HelloWorld

which is puzzling me. Why would my methodVisitor be null? The ASM source code seems to only return null for the methodVisitor when the classVisitor is null, which is not true in my case.

package com.amir.agent.instrumentor;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class amirClassVisitor {

    private byte[] outData = null;

    public amirClassVisitor() {
    }

    public void performInstrumentation(final String className,
                                       final byte[] classAsBytes) {
        final ClassVisitor cl = new ClassVisitor(Opcodes.ASM4) {
            @Override
            public void visit(final int version,
                              final int access,
                              final String name,
                              final String signature,
                              final String superName,
                              final String[] interfaces) {
                System.out.println("Visiting class: "+name);
                System.out.println("Class Major Version: "+version);
                System.out.println("Super class: " + superName);
                super.visit(version, access, name, signature, superName, interfaces);
            }

            @Override
            public void visitOuterClass(final String owner,
                                        final String name,
                                        final String desc) {
                System.out.println("Outer class: "+owner);
                super.visitOuterClass(owner, name, desc);
            }

            @Override
            public AnnotationVisitor visitAnnotation(final String desc,
                                                     final boolean visible) {
                System.out.println("Annotation: "+desc);
                return super.visitAnnotation(desc, visible);
            }

            @Override
            public void visitAttribute(final Attribute attr) {
                System.out.println("Class Attribute: " + attr.type);
                super.visitAttribute(attr);
            }

            @Override
            public void visitInnerClass(final String name,
                                        final String outerName,
                                        final String innerName,
                                        final int access) {
                System.out.println("Inner Class: " + innerName + " defined in " + outerName);
                super.visitInnerClass(name, outerName, innerName, access);
            }

            @Override
            public FieldVisitor visitField(final int access,
                                           final String name,
                                           final String desc,
                                           final String signature,
                                           final Object value) {
                System.out.println("Field: "+name+" "+desc+" value:"+value);
                return super.visitField(access, name, desc, signature, value);
            }

            @Override
            public void visitEnd() {
                System.out.println("Method ends here");
                super.visitEnd();
            }

            @Override
            public MethodVisitor visitMethod(final int access,
                                             final String name,
                                             final String desc,
                                             final String signature,
                                             final String[] exceptions) {
                final MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
                System.out.println("Method: " +name+ " desc = " +desc+ " cv = " +this+ " and mv = " +mv);
                return mv;
            }

            @Override
            public void visitSource(final String source,
                                    final String debug) {
                System.out.println("Source: "+source);
                super.visitSource(source, debug);
            }

        };

        final ClassReader classReader = new ClassReader(classAsBytes);
        classReader.accept(cl, 0);
        System.out.println("Done instrumenting: " +className);

    }

    public byte[] result() {
        return outData;
    }

}

EDIT:

I call this code like this:

public class ClassLoadInterceptor implements ClassFileTransformer {

    @SuppressWarnings("unchecked")
    public byte[] transform(final java.lang.ClassLoader loader,
                            final java.lang.String className,
                            final java.lang.Class classBeingRedefined,
                            final java.security.ProtectionDomain protectionDomain,
                            final byte[] classfileBuffer) throws IllegalClassFormatException {

        if (!(className.startsWith("java") || className.startsWith("sun") || className.startsWith("com/workday/agent"))) {
            WorkdayClassVisitor v = new WorkdayClassVisitor();
            v.performInstrumentation(className, classfileBuffer);
            System.out.println("\t Instrumenting : " +className);
            byte[] instrumented_class = v.result();
            writeOutClassFile("debug", className + ".class", classfileBuffer);
            writeOutClassFile("debug", className + "_instrumented" + ".class", instrumented_class);
            return instrumented_class;
        }

        return classfileBuffer;

    }

Solution

  • What you print out is not “my methodVisitor” but the value returned by the super invocation of visitMethod, in other words the default value returned by ClassVisitor.visitMethod:

    Returns:
       an object to visit the byte code of the method, or null if this class visitor is not interested in visiting the code of this method.

    Since the abstract class ClassVisitor does not implement any action, it has no interest in visiting the method and therefore null is the perfect return value.

    So it’s up to you, to instantiate a new MethodVisitor implementing your desired behavior. Or, since you seem to intend to instrument a class, let your class visitor delegate to a ClassWriter which you pass to the super class constructor. This way you will inherit a replication behavior that you can customize. Then, super.visitMethod will return a non-null method visitor, which will replicate the method.