I have defined a instrumentation of the FileInputStream
constructor using ByteBuddy AgentBuilder.Identified.Extendable
.
It looks like it has no effect.
I was expecting the onEnter
method to be called, but it is not.
I have several ideas about the possible cause of the issue:
How can I debug this situation and understand the cause of the problem?
Here is the source code:
package com.example.javaagent.instrumentation;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import static net.bytebuddy.matcher.ElementMatchers.*;
public class FileInputStreamConstructorInstrumentationTests {
@Test
public void testByteBuddyfileInputStreamConstructor() {
ByteBuddyAgent.install();
AgentBuilder.Identified.Extendable extendableAgentBuilder = new AgentBuilder.Default()
.type(named("java.io.FileInputStream"))
.transform(new AgentBuilder.Transformer.ForAdvice());
extendableAgentBuilder = extendableAgentBuilder.transform(
new AgentBuilder.Transformer.ForAdvice()
.advice(
isConstructor().and(takesArguments(1)).and(takesArgument(0, String.class)),
this.getClass().getName() + "$FileInputStreamCtorString"));
extendableAgentBuilder.installOnByteBuddyAgent();
File file = new File("example.txt");
createFile(file);
try {
// Open a file input stream to read from a file
FileInputStream inputStream = new FileInputStream(file);
// Read the contents of the file and print them to the console
int data = inputStream.read();
while (data != -1) {
System.out.print((char) data);
data = inputStream.read();
}
// Close the input stream to release system resources
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
deleteFile(file);
}
public static class FileInputStreamCtorString {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(@Advice.Argument(0) String name) {
System.out.println("entered " + name);
}
}
private static void deleteFile(File file) {
// 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(File file) {
// 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");
}
}
}
I tried to make the instrumentation using directly a ByteBuddy
instance, and it works as expected.
Like in this example:
ByteBuddyAgent.install();
Foo foo = new Foo();
new ByteBuddy()
.redefine(Bar.class)
.name(Foo.class.getName())
.make()
.load(Foo.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
assertEquals(foo.m(), "bar");
I probably misunderstand how to use correctly the AgentBuilder
.
How about this?
new AgentBuilder.Default()
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemError())
.with(AgentBuilder.Listener.StreamWriting.toSystemError().withTransformationsOnly())
.with(AgentBuilder.InstallationListener.StreamWriting.toSystemError())
.ignore(none())
.with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
.with(AgentBuilder.TypeStrategy.Default.REDEFINE)
.type(named("java.io.FileInputStream"))
.transform((builder, type, classLoader, module) -> builder
.visit(Advice
.to(FileInputStreamCtorString.class)
.on(isConstructor().and(takesArguments(1)).and(takesArgument(0, String.class)))
)
)
.installOnByteBuddyAgent();
You get some logging listeners there. Please also note details like .disableClassFormatChanges()
and .ignore(none())
. Furthermore, I changed the way the advice is applied.
Please also change your sample code to actually use the FileInputStream(String)
constructor you want to intercept, not FileInputStream(File)
:
// Open a file input stream to read from a file
FileInputStream inputStream = new FileInputStream(/*file*/ "example.txt");
For me this works and logs:
[Byte Buddy] BEFORE_INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer@6f1fba17 on sun.instrument.InstrumentationImpl@185d8b6
[Byte Buddy] TRANSFORM java.io.FileInputStream [null, null, Thread[main,5,main], loaded=true]
[Byte Buddy] INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer@6f1fba17 on sun.instrument.InstrumentationImpl@185d8b6
File already exists
entered example.txt
[Example file content]
File deleted successfully
Not being a Byte Buddy expert, I simply copied code from another example I had used a few years ago. I do not fully understand all options, I just remember that I needed them for some cases. In this example, the transformation also seems to work without this part:
.with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
.with(AgentBuilder.TypeStrategy.Default.REDEFINE)
Rafael can explain all of this much better.