javadebuggingintellij-ideainstrumentationbyte-buddy

Breakpoint in ByteBuddy advice class with IntelliJ IDEA


I am looking for a way to debug an advice class within IntelliJ.

In the following code sample, ByteBuddy is replacing the constructor of FileInputStream. The method FileInputStreamCtorString.onEnter is being called as expected, right before the call to the constructor. However, there is a problem during the execution of the onEnter method, apparently related to calling some static method and setting static field.

I would like to put a breakpoint inside the onEnter method for further investigation. How should I configure IntelliJ to make the breakpoint stop as expected?

package com.example.javaagent.instrumentation;

import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.junit.jupiter.api.Test;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.*;
import java.util.logging.Logger;

import static java.util.logging.Level.FINE;
import static net.bytebuddy.matcher.ElementMatchers.*;



public class FileInputStreamConstructorInstrumentationTests2 {

    @Test
    public void testByteBuddyfileInputStreamConstructor() {
        ByteBuddyAgent.install();

        AgentBuilder.Identified.Extendable extendableAgentBuilder = new AgentBuilder.Default()
                .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
                .with(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemError())
                .with(AgentBuilder.Listener.StreamWriting.toSystemError().withTransformationsOnly())
                .with(AgentBuilder.InstallationListener.StreamWriting.toSystemError())
                .ignore(none())
                .type(named("java.io.FileInputStream"))
                .transform(new AgentBuilder.Transformer.ForAdvice());

        extendableAgentBuilder = extendableAgentBuilder.transform(
                new AgentBuilder.Transformer.ForAdvice()
                        .include(Thread.currentThread().getContextClassLoader())
                        .advice(
                                isConstructor().and(takesArguments(1)).and(takesArgument(0, String.class)),
                                this.getClass().getName() + "$FileInputStreamCtorString"));

        extendableAgentBuilder.installOnByteBuddyAgent();

        String filename = "example.txt";
        createFile(filename);
        try {
            FileInputStream inputStream = new FileInputStream(filename);
            int data = inputStream.read();
            while (data != -1) {
                System.out.print((char) data);
                data = inputStream.read();
            }
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        deleteFile(filename);
    }

    public static class FileInputStreamCtorString {
        public static boolean iveBeenHere = false;

        @Advice.OnMethodEnter(suppress = Throwable.class)
        public static void onEnter(@Advice.Argument(0) String name) {
            System.out.println("FileInputStreamCtorString: entered with param "+ name);
            System.out.println("FileInputStreamCtorString: second log line");
            JustPrint.sayHello();
            System.out.println("FileInputStreamCtorString: after calling static method");
            iveBeenHere = true;
            System.out.println("FileInputStreamCtorString: after setting static field");
        }
    }

    public static class JustPrint{
        public static void sayHello(){
            System.out.println("Did I say 'hello'?");
        }
    }

    private static void deleteFile(String filename) {
        File file = new File(filename);
        // Check if the file exists
        if (file.exists()) {
            // If the file exists, delete it
            if (file.delete()) {
                System.out.println("File deleted successfully");
            } else {
                System.out.println("Failed to delete file");
            }
        } else {
            System.out.println("File does not exist");
        }
    }

    private static void createFile(String filename) {
        File file = new File(filename);
        // Check if the file already exists
        if (!file.exists()) {
            // If the file does not exist, create a new file
            try {
                file.createNewFile();
                System.out.println("File created successfully");
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("File already exists");
        }
    }
}


When running the test testByteBuddyfileInputStreamConstructor, I get the following output:

[Byte Buddy] BEFORE_INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport@f9879ac on sun.instrument.InstrumentationImpl@37f21974
[Byte Buddy] REDEFINE BATCH #0 [1 of 1 type(s)]
[Byte Buddy] TRANSFORM java.io.FileInputStream [null, module java.base, Thread[Test worker,5,main], loaded=true]
[Byte Buddy] REDEFINE COMPLETE 1 batch(es) containing 1 types [0 failed batch(es)]
[Byte Buddy] INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport@f9879ac on sun.instrument.InstrumentationImpl@37f21974
File created successfully
FileInputStreamCtorString: entered with param example.txt
FileInputStreamCtorString: second log line
File deleted successfully

So, I'm sure that the following source line is reached and I would like the debugger to stop there:

System.out.println("FileInputStreamCtorString: entered with param "+ name);


Solution

  • Byte Buddy inlines advice code, meaning that your code is never executed, it only serves as a copy-paste template. You can set the delegate property in the enter and exit annotations to true if you wanted to invoke the advice methods instead.