0

I'm writing a gradle plugin for my lib. https://github.com/shehabic/sherlock, I need to inject a network interceptor at compilation time in the byte code of OkHttp Client (https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/OkHttpClient.java) to specific I would like to inject the following line in Java:

this.interceptors.add(new com.shehabic.sherlock.interceptors(new SherlockOkHttpInterceptor())

https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/OkHttpClient.java#L1068

I have written the plugin the transformer already and here's my class writer:

public class SherlockClassWriter {

    ClassReader reader;
    ClassWriter writer;
    PublicizeMethodAdapter pubMethAdapter;
    final static String CLASSNAME = "okhttp3.OkHttpClient";

    public SherlockClassWriter() {
        try {
            reader = new ClassReader(CLASSNAME);
            writer = new ClassWriter(reader, 0);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    public SherlockClassWriter(byte[] contents) {
        reader = new ClassReader(contents);
        writer = new ClassWriter(reader, 0);
    }

    public static void main(String[] args) {
        SherlockClassWriter ccw = new SherlockClassWriter();
        ccw.publicizeMethod();
    }

    public byte[] publicizeMethod() {
        pubMethAdapter = new PublicizeMethodAdapter(writer);
        reader.accept(pubMethAdapter, 0);
        return writer.toByteArray();
    }

    public class PublicizeMethodAdapter extends ClassVisitor {

        TraceClassVisitor tracer;
        PrintWriter pw = new PrintWriter(System.out);

        public PublicizeMethodAdapter(ClassVisitor cv) {
            super(ASM4, cv);
            this.cv = cv;
            tracer = new TraceClassVisitor(cv, pw);
        }

        @Override
        public MethodVisitor visitMethod(
            int access,
            String name,
            String desc,
            String signature,
            String[] exceptions
        ) {
            if (name.equals("build")) {
                MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
                // call method in java:
                // this.interceptors.add(new com.shehabic.sherlock.interceptors(new SherlockOkHttpInterceptor())
            }
            return tracer.visitMethod(access, name, desc, signature, exceptions);
        }
    }
}

a similar method that adds interceptors has a bytecode as follows:

aload_0
getfield #4 <okhttp3/OkHttpClient$Builder.interceptors>
aload_1
invokeinterface #117 <java/util/List.add> count 2
pop
aload_0

My questions are: 1.How do I inject more code into a method? even if Bytecode.

Update Here is my working solution, based on the answer: https://github.com/shehabic/sherlock/blob/creating-plugin-to-intercept-all-okhttp-connections/sherlock-plugin/src/main/java/com/shehabic/sherlock/plugin/SherlockClassWriter.java

1
  • 1
    asm.ow2.io/asm4-guide.pdf is a great resource for this sort of stuff. I'd recommend using the Tree API (using MethodNode) as it makes it much easier to actually see what's happening. Commented Jan 15, 2019 at 11:45

1 Answer 1

3

There is example code to insert your line at the beggining of the function

public class YourClassVisitor extends ClassVisitor {
    public YourClassVisitor(ClassVisitor cv) {
        super(Opcodes.ASM5, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        if (name.equals("targetName")) {
            return new YourMethodVisitor(super.visitMethod(access, name, desc, signature, exceptions));
        }
        return super.visitMethod(access, name, desc, signature, exceptions);
    }


    private static class YourMethodVisitor extends MethodVisitor {
        public YourMethodVisitor(MethodVisitor mv) {
            super(Opcodes.ASM5, mv);
        }

        // This method will be called before almost all instructions
        @Override
        public void visitCode() {
            // Default implementation is empty. So we haven't to call super method

            // Puts 'this' on top of the stack. If your method is static just delete it
            visitVarInsn(Opcodes.ALOAD, 0);
            // Takes instance of class "the/full/name/of/your/Class" from top of the stack and put value of field interceptors
            // "Ljava/util/List;" is just internal name of java.util.List
            // If your field is static just replace GETFIELD with GETSTATIC
            visitFieldInsn(Opcodes.GETFIELD, "the/full/name/of/your/Class", "interceptors", "Ljava/util/List;");
            // Before we call add method of list we have to put target value on top of the stack
            // New object creation starts with creating not initialized instance of it
            visitTypeInsn(Opcodes.NEW, "com/shehabic/sherlock/interceptors");
            // Than we just copy it
            visitInsn(Opcodes.DUP);
            visitTypeInsn(Opcodes.NEW, "example/path/to/class/SherlockOkHttpInterceptor");
            visitInsn(Opcodes.DUP);
            // We have to call classes constructor
            // Internal name of constructor - <init>
            // ()V - signature of method. () - method doesn't have parameters. V - method returns void
            visitMethodInsn(Opcodes.INVOKESPECIAL, "example/path/to/class/SherlockOkHttpInterceptor", "<init>", "()V", false);
            // So on top of the stack we have initialized instance of example/path/to/class/SherlockOkHttpInterceptor
            // Now we can call constructor of com/shehabic/sherlock/interceptors
            visitMethodInsn(Opcodes.INVOKESPECIAL, "com/shehabic/sherlock/interceptors", "<init>", "(Lexample/path/to/class/SherlockOkHttpInterceptor;)V", false);
            // So on top of the stack we have initialized instance of com/shehabic/sherlock/interceptors
            // Now we can put it into list
            visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true);



        }
    }

}

There is example of using class visitor

        byte[] cache = null;
        try (FileInputStream in = new FileInputStream("C:\\Users\\JustAGod\\Projects\\gloomymods\\BuildTools\\BytecodeTools\\out\\production\\classes\\gloomyfolken\\Kek.class")) {
            ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
            ClassReader reader = new ClassReader(in);
            reader.accept(new YourClassVisitor(writer), ClassReader.EXPAND_FRAMES);
            cache = writer.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try(FileOutputStream out = new FileOutputStream("C:\\Users\\JustAGod\\Projects\\gloomymods\\BuildTools\\BytecodeTools\\out\\production\\classes\\gloomyfolken\\Kek.class")) {
            out.write(cache);
        } catch (IOException e) {
            e.printStackTrace();
        }

I am really sorry for my English.

Sign up to request clarification or add additional context in comments.

1 Comment

I'll try that and if it works I'll mark it as the right answer

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.