Java 8 appears to generate classes to represent lambda expressions. For instance, the code:
Runnable r = app::doStuff;
Manifests, roughly, as:
// $FF: synthetic class
final class App$$Lambda$1 implements Runnable {
private final App arg$1;
private App$$Lambda$1(App var1) {
this.arg$1 = var1;
}
private static Runnable get$Lambda(App var0) {
return new App$$Lambda$1(var0);
}
public void run() {
this.arg$1.doStuff();
}
}
As I understand this, the code is generated at runtime. Now, suppose one wanted to inject code into the run
method of the above class. Experiments thus far yield a mix of NoClassDefFound
and VerifyError
:
java.lang.NoClassDefFoundError: App$$Lambda$2
at App$$Lambda$2/1329552164.run(Unknown Source)
at App.main(App.java:9)
Caused by: java.lang.ClassNotFoundException: App$$Lambda$2
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 2 more
This is running against:
$ java -version
java version "1.8.0_51"
Java(TM) SE Runtime Environment (build 1.8.0_51-b16)
Java HotSpot(TM) 64-Bit Server VM (build 25.51-b03, mixed mode)
This is even before pushing any new bytecode into the class.
Is this expected? Smells like a JDK bug, but I'm happy to be wrong!
To me, this seems like a bug in the JVM. The system class loader attempts to locate the transformed class by its name. However, lambda expressions are loaded via anonymous class loading where the following condition:
yields a
ClassNotFoundException
resulting in theNoClassDefError
. The class is not considered a real class and such anonyoumous classes are for example not passed to aClassFileTransformer
outside of a retransform.All in all, the instrumentation API feels a bit buggy to me when dealing with anonymous classes. Similarly,
LambdaForm
s are passed toClassFileTransformer
s but with all arguments but theclassFileBuffer
set tonull
what breaks the transformer class's contract.For your example, the problem seems to be that you return
null
; the problem goes away when returning theclassFileBuffer
what is a no-op. This is however not what theClassFileTransformer
suggests, where returningnull
is the recommended way of doing this:To me, this seems like a bug in HotSpot. You should report this issue to the OpenJDK.
All in all, it is perfectly possible to instrument anonymously loaded classes as I demonstrate in my code manipulation library Byte Buddy. It requires some unfortunate tweaks compared to normal instrumentation but the runtime supports it. Here is an example that successfully runs as a unit test within the library:
Bug submission was accepted by folks at Oracle, and is being tracked as JDK-8145964. This isn't exactly a solution, but appears to be a real runtime issue.