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