Java 8 appears to generate classes to represent lambda expressions. For instance, the code:
Runnable r = app::doStuff;
Manifests, roughly
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.
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:
clazz.getClassLoader()
.loadClass(clazz.getName().substring(0, clazz.getName().indexOf('/')))
yields a ClassNotFoundException
resulting in the NoClassDefError
. The class is not considered a real class and such anonyoumous classes are for example not passed to a ClassFileTransformer
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 to ClassFileTransformer
s but with all arguments but the classFileBuffer
set to null
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 the classFileBuffer
what is a no-op. This is however not what the ClassFileTransformer
suggests, where returning null
is the recommended way of doing this:
a well-formed class file buffer (the result of the transform), or
null
if no transform is performed.
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:
Callable<String> lambda = () -> "foo";
Instrumentation instrumentation = ByteBuddyAgent.install();
ClassReloadingStrategy classReloadingStrategy = ClassReloadingStrategy.of(instrumentation)
.preregistered(lambda.getClass());
ClassFileLocator classFileLocator = ClassFileLocator.AgentBased.of(instrumentation,
lambda.getClass());
assertThat(lambda.call(), is("foo"));
new ByteBuddy()
.redefine(lambda.getClass(), classFileLocator)
.method(named("call"))
.intercept(FixedValue.value("bar"))
.make()
.load(lambda.getClass().getClassLoader(), classReloadingStrategy);
assertThat(lambda.call(), is("bar"));