NoClassDefFound -- new class cannot access the class whose classloader was used for injection
In a nutshell, I have a class B$1 that I create from scatch at runtime and that has a non-static method calling a static method of class B$0 (there is no inheritance between the two classes, although they may share the parent, but even that's not a necessity).
Injection is done by
Class b0 = Class.forName("B$0");
ClassLoadingStrategy.Default.INJECTION.load(b0.getClassLoader(),
Collections.singletonMap(newClass.getTypeDescription(), finishedBytecode));
but if I immediately after that injection test the method
try {
Class c = Class.forName("B$1");
Object instance = c.newInstance();
c.getMethod("move", Object.class).invoke(instance, new Object());
} catch (Exception e) {
e.printStackTrace();
}
I get a NoClassDefFoundError: B$0.
Why? And how do I resolve this?
(For more details about the use case, please see https://stackoverflow.com/questions/49232664/how-to-load-this-class-in-the-correct-way .)
Oh, and if at all relevant: using Java 1.7.0_65 and ByteBuddy 1.7.9
The NoClassDefFoundError might be misleading and point to an error in your instrumentation. It could be a erification error in disguise. Are you instrumenting your code using Byte Buddy? I am wondering why you do not operate on a dynamic type.
I would need to see more of your example to provide better help. I think the actual problem is in the byte code generation.
Looks like you were right about the problem's lying with the code generation.
Couldn't post more code because it's proprietary and frankly, too darn large. Didn't help that all the "minimal working examples" I tried to construct actually worked ...
The issue turned out to be disappointingly trivial: a string constant used dots instead of slashes to separate package names. Since that constant is used by ASM and looking for foo/bar/Baz is not the same as looking for foo.bar.Baz, it failed.
Yeah, really stupid. Still, thanks.
As for why I'm not using a dynamic type, it's that we already have ASM visitors that do part of the transformation required for the new class.
The bytebuddy tutorial, however, says
if you really need to create byte code with jump instructions, make sure to add the correct stack map frames using ASM since Byte Buddy will not automatically include them for you.
and quite frankly, that's too much of a hassle (and in my case actually bars me from using the ASM visitors we already have because they rely on the fact that stack frames are computed).
It's much more convenient to write something like
public class MethodAdder extends ClassVisitor implements Opcodes {
private MethodAdder(int api, ClassVisitor cv) {
super(api, cv);
}
public static byte[] instrument(byte[] rawByteCode){
ClassReader cr = new ClassReader(rawByteCode);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
FooTransformer ft = new FooTransformer(ASM5,cw);
BarTransformer bt = new BarTransformer(ASM5,ft);
MethodAdder ma = new MethodAdder(ASM5,bt);
cr.accept(ma, ClassReader.EXPAND_FRAMES);
return cw.toByteArray();
}
...
}
and let ASM figure out the rest.
You can still use ASM with Byte Buddy in this case, have a look at AsmVisitorWrapper.ForDeclaredMethods. you can register frame computation in there and still use ASM for the method body.
Glad you figured it out.
In that case, may I suggest adding this information to the tutorial? Because I read "ByteBuddy will not" as "ByteBuddy refuses to" and stopped considering ByteBuddy for transformations right then and there.
Yes, please! Could you do a PR on the gh-pages branch with a suggestion?
Can do, but it will take a while. I'll need to invest time into learning the "how-to" well enough to write anything useful. Time I currently just don't have. Should be able to tackle this in about two, three months or so.
No worries, if I find time first, I will do it, otherwise, I can easily just merge your PR.