raphw / byte-buddy

Runtime code generation for the Java virtual machine.
https://bytebuddy.net
Apache License 2.0
6.22k stars 800 forks source link

UnsupportedOperationException when creating class with Adoptium JDK 21 #1567

Open trancexpress opened 9 months ago

trancexpress commented 9 months ago

To reproduce:

  1. Download Adoptium JDK 21: https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.1%2B12/OpenJDK21U-jdk_x64_linux_hotspot_21.0.1_12.tar.gz
  2. Run snippet (everything is on the classpath, not the module path):
    
    package test;

import net.bytebuddy.ByteBuddy; import net.bytebuddy.description.modifier.Ownership; import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;

public class Test {

public static void main(String[] args) {
    Class<? extends A> loadClass = new ByteBuddy().subclass(A.class)
            .name("B")
            .defineField("flag", boolean.class, Visibility.PRIVATE, Ownership.MEMBER).make()
            .load(Test.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION).getLoaded();
    System.out.println(loadClass);
}

}

3. Observe an exception is thrown:

Exception in thread "main" java.lang.UnsupportedOperationException: Cannot define class using reflection: Unable to make protected java.lang.Package java.lang.ClassLoader.getPackage(java.lang.String) accessible: module java.base does not "opens java.lang" to unnamed module @1d9d9fed at net.bytebuddy.dynamic.loading.ClassInjector$UsingReflection$Dispatcher$Initializable$Unavailable.defineClass(ClassInjector.java:472) at net.bytebuddy.dynamic.loading.ClassInjector$UsingReflection.injectRaw(ClassInjector.java:284) at net.bytebuddy.dynamic.loading.ClassInjector$AbstractBase.inject(ClassInjector.java:118) at net.bytebuddy.dynamic.loading.ClassLoadingStrategy$Default$InjectionDispatcher.load(ClassLoadingStrategy.java:241) at net.bytebuddy.dynamic.loading.ClassLoadingStrategy$Default.load(ClassLoadingStrategy.java:148) at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:101) at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:6317) at test.Test.main(Test.java:14)

raphw commented 9 months ago

That is expected. Use ClassLoadingStrategy.UsingLookup instead. Injection used unsafe what is restricted on that version.

trancexpress commented 9 months ago

Alright, switching to using a lookup works for both JDK 17 and JDK 21:

    public static void main(String[] args) throws IllegalAccessException {
        Class<? extends A> c = A.class;
        Lookup lookup = MethodHandles.privateLookupIn(c, MethodHandles.lookup());
        Class<? extends A> loadClass = new ByteBuddy().subclass(c).name("test.B")
                .defineField("flag", boolean.class, Visibility.PRIVATE, Ownership.MEMBER).make()
                .load(Test.class.getClassLoader(), ClassLoadingStrategy.UsingLookup.of(lookup)).getLoaded();
        System.out.println(loadClass);
    }

Thanks for the help! I guess this issue can be closed.

Injection used unsafe what is restricted on that version.

@raphw , do you remember which JDK change causes the difference in behavior between 17 and 21?

raphw commented 9 months ago

It's strict encapsulation that is enforced with Java 21.