byte-buddy icon indicating copy to clipboard operation
byte-buddy copied to clipboard

UnsupportedOperationException when creating class with Adoptium JDK 21

Open trancexpress opened this issue 2 years ago • 3 comments

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);
	}

}
  1. 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)

trancexpress avatar Nov 30 '23 18:11 trancexpress

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

raphw avatar Nov 30 '23 19:11 raphw

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?

trancexpress avatar Dec 01 '23 14:12 trancexpress

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

raphw avatar Dec 02 '23 07:12 raphw