I'm trying to log every call, returned objects and exceptions thrown in methods and constructors using a ByteBuddy (v1.7.9) java agent, without iterfering with the normal functioning of the instrumented code.
My current instantiation of the agent is
new AgentBuilder.Default()
.with(AgentBuilder.Listener.StreamWriting.toSystemOut())
.type((typeDescription, classLoader, module, classBeingRedefined, protectionDomain) ->
matcher.matchesIncoming(typeDescription.getTypeName()))
.transform((builder, typeDescription, classLoader, javaModule) -> builder
.visit(Advice.to(CustomAdvicer.class).on(ElementMatchers.any())))
.installOn(inst);
I have started with the simplest "advicer",
public class CustomAdvicer {
@Advice.OnMethodEnter
public static void enter(@Advice.Origin String origin) {
System.out.println("Entering " + origin);
}
@Advice.OnMethodExit(onThrowable = Throwable.class)
public static void exit(
@Advice.Return(typing = Assigner.Typing.DYNAMIC) @RuntimeType Object value,
@Advice.Origin String origin,
@Advice.Thrown Throwable thrown) {
System.out.println("Exiting " + origin);
}
}
However when I run the program I get an exception from bytebuddy:
[Byte Buddy] ERROR some.pack.Thrower [sun.misc.Launcher$AppClassLoader@18b4aac2, null, loaded=false]
java.lang.IllegalStateException: Cannot catch exception during constructor call for public some.pack.Thrower() throws java.lang.Exception
at net.bytebuddy.asm.Advice.doWrap(Advice.java:515)
at net.bytebuddy.asm.Advice.wrap(Advice.java:470)
at net.bytebuddy.asm.AsmVisitorWrapper$ForDeclaredMethods$Entry.wrap(AsmVisitorWrapper.java:481)
at net.bytebuddy.asm.AsmVisitorWrapper$ForDeclaredMethods$DispatchingVisitor.visitMethod(AsmVisitorWrapper.java:562)
at net.bytebuddy.jar.asm.ClassVisitor.visitMethod(ClassVisitor.java:327)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$RedefinitionClassVisitor.visitMethod(TypeWriter.java:3801)
at net.bytebuddy.jar.asm.ClassReader.readMethod(ClassReader.java:1020)
at net.bytebuddy.jar.asm.ClassReader.accept(ClassReader.java:698)
at net.bytebuddy.jar.asm.ClassReader.accept(ClassReader.java:500)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining.create(TypeWriter.java:2941)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:1633)
at net.bytebuddy.dynamic.scaffold.inline.RebaseDynamicTypeBuilder.make(RebaseDynamicTypeBuilder.java:200)
at net.bytebuddy.agent.builder.AgentBuilder$Default$Transformation$Simple$Resolution.apply(AgentBuilder.java:8905)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.doTransform(AgentBuilder.java:9306)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:9269)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.access$1300(AgentBuilder.java:9047)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$LegacyVmDispatcher.run(AgentBuilder.java:9625)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$LegacyVmDispatcher.run(AgentBuilder.java:9575)
at java.security.AccessController.doPrivileged(Native Method)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:9194)
at sun.instrument.TransformerManager.transform(Unknown Source)
at sun.instrument.InstrumentationImpl.transform(Unknown Source)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(Unknown Source)
at java.security.SecureClassLoader.defineClass(Unknown Source)
at java.net.URLClassLoader.defineClass(Unknown Source)
at java.net.URLClassLoader.access$100(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at Main.main(Main.java:23)
So, what should I do in order to log the exceptions thrown inside constructors, bearing in mind that it must not interfere with the original code?
By the way, the Thrower class is a silly class I wrote to test this case:
package some.pack;
public class Thrower {
public Thrower() throws Exception {
throw new Exception("By courtesy of thrower! ;)");
}
}
The problem is that a constructor has an implicit first instruction which is the invocation of another or a super constructor. Your Thrower class really looks like this:
public class Thrower {
public Thrower() throws Exception {
super();
throw new Exception("By courtesy of thrower! ;)");
}
}
If you wanted to wrap the entire call in a try-catch block, this would yield this:
public class Thrower {
public Thrower() throws Exception {
try {
super();
throw new Exception("By courtesy of thrower! ;)");
} catch (Exception e) {
...
}
}
}
But this is not legal in the JVM and therefore, Byte Buddy does not allow it. There is neither a good way to exclude the super constructor call as this being the first call is only a Java language convention but byte code allows more arbitrary combinations. As you cannot know what language a class comes from, Byte Buddy does not try any tricks here and simply does not allow it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With