KnotClassLoader does not remove lambda functions from @Environment methods
See original GitHub issueThe problem
I recently found myself in a situation, where I have a method, annotated with @Environment(EnvType.Client)
, which contains a lambda expression, referencing a client-only class (in my case EntityRenderer
).
The code produces no exceptions on the client, but on a dedicated server, I get this exception.
java.lang.RuntimeException: Cannot load class net.minecraft.client.render.entity.EntityRenderer in environment type SERVER
The interesting thing is, if I replace the lambda expression with an anonymous class executing the same logic, no exception is thrown.
The suspected cause
I suppose this problem occurs, because the KnotClassLoader does not respect the compile-time generated lambda method in it’s environment stripping process.
I found that, in the bytecode of the class, there is a generated lambda function:
...
// access flags 0x100A
private static synthetic lambda$registerClient$0(Lnet/minecraft/client/render/entity/EntityRenderDispatcher;Lnet/fabricmc/fabric/api/client/rendereregistry/v1/EntityRendererRegistry$Context;)Lnet/minecraft/client/render/entity/EntityRenderer;
...
When using an anonymous class, an inner class is generated instead:
...
public static abstract INNERCLASS net/fabricmc/fabric/api/client/rendereregistry/v1/EntityRendererRegistry$Factory net/fabricmc/fabric/api/client/rendereregistry/v1/EntityRendererRegistry Factory
...
I guess the environment strip happens on the inner class, but not on the lambda method. This causes the class loader to throw an exception, because the lambda causes class loading of client-only classes, in my case.
Details
- fabric-loader version 0.11.3
- Minecraft version 1.16.5
- Fabric version 0.32.5+1.16
- Java version 1.8 (AdoptOpenJDK\jdk-8.0.282.8-hotspot)
- Operating System: Windows 10
Steps to reproduce
In my case, I created “modules” which have separate register()
methods which are called from the common entry and the client entry of my mod.
These “modules” all implement two interfaces, declaring those register methods.
The client method is annotated with @Environment(EnvType.CLIENT)
, of course.
The general steps would be:
- Create a class with a client-only method, as well with some other use case (why one would load the class on a dedicated server in the first place, see my example)
- Call a method which requires a functional interface, which can be represented as a lambda function
- Reference a client-only class from the lambda
- Run a dedicated server
My Repository for reference
My version of the class, in a real use case, can be found here. NOTE however, that the version in this commit does not break. One has to modify these lines:
EntityRendererRegistry.INSTANCE.register(stonelingType, new EntityRendererRegistry.Factory() {
@Override
public EntityRenderer<? extends Entity> create(EntityRenderDispatcher manager, EntityRendererRegistry.Context context) {
return new StonelingRenderer(manager);
}
});
to these:
EntityRendererRegistry.INSTANCE.register(stonelingType, (manager, context) -> new StonelingRenderer(manager));
The use case is well justified, in my opinion, since one might want to organize such module based logic (like entity registering) in one class.
Issue Analytics
- State:
- Created 2 years ago
- Comments:5 (4 by maintainers)
Top GitHub Comments
There is still a case to be made for resolving this issue. The conventional and overall safer method is to isolate client-only logic in separate classes (which you may optionally annotate with
@Environment
). However, in cases where Minecraft unavoidably bridges client-side logic from common logic, such as cases where you have to override various rendering-relatedBlock
methods, method-level@Environment
annotations are more appropriate. For these cases it might be nice to avoid class loading errors caused by synthetic lambda methods.Last I checked, MinecraftForge deals with this problem at runtime in their environment stripping transformer by transitively stripping synthetic methods that are solely referenced by
LambdaMetaFactory
invokedynamic
s in stripped methods. This might be an option for Fabric Loader, too, but it could have a noticeable class loading performance impact.In my opinion, transitively applying
@Environment
annotations to synthetic lambda methods as a compile-time processing step would be a better option.Thank you for making this clear, I’ll consider separating my client and server code from now on.