javagenericsjvmclassloaderbyte-buddy

Prevent Byte Buddy from reifying type arguments when implementing parameterized type


This is a long shot, but I got lots of great Byte Buddy advice here before, so I'm hoping I get lucky again. The background is a tricky bootstrapping scenario for an experimental compiler, where the compiler needs to execute some code that was compiled earlier in the same step. There is an interface that extends a parameterized interface and binds a type argument:

interface Reals extends Provider<Real>
{
  //...
}

There is also an "implementation", which is really another interface with a bunch of default methods. Due to the tricky bootstrapping scenario I mentioned, there is no extension or implementation relationship to the other interface. Byte Buddy is used for creating an actual implementation class at runtime that links the two together and ensures that one actually "implements" the other (hence all the unusual annotations):

@Implements("lang.Reals")
@bind interface Reals extends Provider<Object>
{
  //...
}

Notably, the implementation does not use the Real type argument (again bootstrapping reasons), and the functionality that is needed during the bootstrapping process does not need the Real type either. The Byte Buddy configuration under the hood is:

ByteBuddy byteBuddy = new ByteBuddy(JAVA_V9)
  . with(TypeValidation.DISABLED)
  . with(MethodGraph.Compiler.Default.forJVMHierarchy)

Bridge methods are generated explicitly.

So, after this lengthy preamble, when the code runs for this particular scenario, I get the following exception:

java.lang.TypeNotPresentException: Type lang.Real not present
at java.base/sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:117)
at java.base/sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:125)
at java.base/sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49)
at java.base/sun.reflect.generics.visitor.Reifier.reifyTypeArguments(Reifier.java:68)
at java.base/sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:138)
at java.base/sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49)
at java.base/sun.reflect.generics.repository.ClassRepository.computeSuperInterfaces(ClassRepository.java:117)
at java.base/sun.reflect.generics.repository.ClassRepository.getSuperInterfaces(ClassRepository.java:95)
at java.base/java.lang.Class.getGenericInterfaces(Class.java:1211)
at net.bytebuddy.description.type.TypeList$Generic$OfLoadedInterfaceTypes$TypeProjection.resolve(TypeList.java:823)
at net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection.accept(TypeDescription.java:6297)
at net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection$WithResolvedErasure.resolve(TypeDescription.java:6949)
at net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection.accept(TypeDescription.java:6297)
at net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default.doAnalyze(MethodGraph.java:746)
at net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default.analyze(MethodGraph.java:710)
at net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default.doAnalyze(MethodGraph.java:746)
at net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default.compile(MethodGraph.java:668)
at net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$AbstractBase.compile(MethodGraph.java:519)
at net.bytebuddy.dynamic.scaffold.MethodRegistry$Default.prepare(MethodRegistry.java:472)
at net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder.toTypeWriter(SubclassDynamicTypeBuilder.java:212)
at net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder.toTypeWriter(SubclassDynamicTypeBuilder.java:203)
at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$UsingTypeWriter.make(DynamicType.java:4055)
at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase.make(DynamicType.java:3739)
at pro.projo.internal.rcg.RuntimeCodeGenerationHandler.generateImplementation(RuntimeCodeGenerationHandler.java:208)
at pro.projo.internal.rcg.RuntimeCodeGenerationHandler.lambda$getImplementationOf$0(RuntimeCodeGenerationHandler.java:183)
at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1740)
at pro.projo.internal.rcg.RuntimeCodeGenerationHandler.getImplementationOf(RuntimeCodeGenerationHandler.java:183)
at pro.projo.Projo.getImplementationClass(Projo.java:496)
at pro.projo.Projo.getImplementationClass(Projo.java:468)
at natives.bootstrap.Bootstrap.bindNativeImplementation(Bootstrap.java:139)

The exception message is spot on: the type Real is indeed not present. The class file for it has not been written yet, and the compiler is at this very moment working on creating it.

The problem comes to a head when Byte Buddy tries to resolve the interfaces of the original Reals interface (TypeList.java:822):

            protected TypeDescription.Generic resolve() {
                java.lang.reflect.Type[] type = this.type.getGenericInterfaces();

The presence of a <Real> type argument triggers loading of the corresponding class. Byte Buddy handles this is absolutely correctly, I'm not saying there is any kind of bug here. However, for the scenario at hand, I would like to replace this code with just calling getInterfaces(), which would presumably not trigger an attempt to load the type argument of Provider<Real>. The way the Reals provider will be used is completely generic, that is it will only ever be invoked via bridge methods, not via any methods that have the type parameter replaced by an actual Real type as the return type or parameter type (in fact, only the bridge methods will be generated which will contain the actual method implementation rather than the checkcast and redirect to the implementation method).

The long story short is that I'm looking for a way to customize this behavior in Byte Buddy so that I can create code at runtime that will treat interfaces as raw types and not force loading the Real type prematurely.

What I've tried so far (all without success and only leading to all kinds of other exceptions):

  1. Replacing the TypeDescription.ForLoadedType (which, it seems, is the reason that reflection is used during type resolution) by a TypeDescription.Latent
  2. Using a customized subclass of TypeDescription.Latent that overrides some of the methods that caused other exceptions
  3. Replacing the MethodGraph.Compiler.Default.forJVMHierarchy with a custom method graph compiler implementation that attempts to steer around this issue
  4. Implemented a TypeDescription.Generic.Visitor<Generic> to be used by the method graph compiler, again attempting to replace some of the default code with customized alternative handling
  5. Various combinations of the above

Is there a known strategy that would achieve what I'm trying to do here? Are there any other Byte Buddy options that can help with achieving what I'm trying to do here?


Solution

  • You would need to create a TypePool where you resolve types either via a class loader or as a dummy description. TypePools can be set up in a hierarchy, so you'd need to create a base implementation that returns a dummy. Note that loaded types will resolve their members via the class loader mechanism and cannot return to using a type pool, so you need to avoid describing loaded types.