diff --git a/.gitignore b/.gitignore index 7bde298fa..7f140ee59 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,9 @@ build/ /.settings/ .idea *.log + +# Java 25 migration: local test run dirs and IDE module files +run_j17_test/ +run_j25_test/ +run_prod/ +*.iml diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/JavaPluginLoaderMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/JavaPluginLoaderMixin.java index 9a539d68a..bc9c71774 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/JavaPluginLoaderMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/JavaPluginLoaderMixin.java @@ -1,11 +1,9 @@ package io.izzel.arclight.common.mixin.bukkit; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import io.izzel.arclight.api.Unsafe; import io.izzel.arclight.common.bridge.bukkit.JavaPluginLoaderBridge; import io.izzel.arclight.common.bridge.bukkit.PluginClassLoaderBridge; import io.izzel.arclight.common.mod.server.ArclightServer; +import io.izzel.arclight.common.mod.util.EventExecutorGenerator; import io.izzel.arclight.i18n.ArclightConfig; import org.apache.commons.lang3.Validate; import org.bukkit.Server; @@ -16,24 +14,14 @@ import org.bukkit.plugin.*; import org.bukkit.plugin.java.JavaPluginLoader; import org.jetbrains.annotations.NotNull; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; import org.spongepowered.asm.mixin.*; import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.gen.Invoker; import java.lang.reflect.Constructor; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.net.URLClassLoader; import java.util.*; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; @Mixin(value = JavaPluginLoader.class, remap = false) @@ -46,15 +34,6 @@ public abstract class JavaPluginLoaderMixin implements JavaPluginLoaderBridge { @Accessor("loaders") public abstract List arclight$getLoaders(); // @formatter:on - private static final AtomicInteger COUNTER = new AtomicInteger(); - private static final Cache> EXECUTOR_CACHE = CacheBuilder.newBuilder() - .expireAfterAccess(1, TimeUnit.HOURS) - .build(); - private static final String HIDDEN_FORM = - Float.parseFloat(System.getProperty("java.class.version")) < 57 - ? "Ljava/lang/invoke/LambdaForm$Hidden;" - : "Ljdk/internal/vm/annotation/Hidden;"; - /** * @author InitAuther97 * @reason Support plugin class loader isolation @@ -156,7 +135,7 @@ public Map, Set> createRegisteredList // final CustomTimingsHandler timings = new CustomTimingsHandler("Plugin: " + plugin.getDescription().getFullName() + " Event: " + listener.getClass().getName() + "::" + method.getName() + "(" + eventClass.getSimpleName() + ")", pluginParentTimer); // Spigot try { - Class executorClass = createExecutor(method, eventClass); + Class executorClass = EventExecutorGenerator.createExecutor(method, eventClass); Constructor constructor = executorClass.getDeclaredConstructor(); constructor.setAccessible(true); EventExecutor executor = constructor.newInstance(); @@ -168,110 +147,4 @@ public Map, Set> createRegisteredList return ret; } - @SuppressWarnings("unchecked") - private Class createExecutor(Method method, Class eventClass) throws ExecutionException { - return EXECUTOR_CACHE.get(method, () -> { - ClassWriter cv = new ClassWriter(ClassWriter.COMPUTE_MAXS); - cv.visit(Opcodes.V1_8, - Opcodes.ACC_SUPER + Opcodes.ACC_SYNTHETIC + Opcodes.ACC_FINAL, - Type.getInternalName(method.getDeclaringClass()) + "$$arclight$" + COUNTER.getAndIncrement(), - null, - Type.getInternalName(Object.class), - new String[]{Type.getInternalName(EventExecutor.class)} - ); - cv.visitOuterClass(Type.getInternalName(method.getDeclaringClass()), null, null); - createConstructor(cv); - createImpl(method, eventClass, cv); - cv.visitEnd(); - return (Class) Unsafe.defineAnonymousClass(method.getDeclaringClass(), cv.toByteArray(), null); - }); - } - - private void createConstructor(ClassVisitor cv) { - MethodVisitor mv = cv.visitMethod( - Opcodes.ACC_PRIVATE, - "", - "()V", - null, null); - mv.visitCode(); - mv.visitVarInsn(Opcodes.ALOAD, 0); - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), "", "()V", false); - mv.visitInsn(Opcodes.RETURN); - mv.visitMaxs(-1, -1); - mv.visitEnd(); - } - - private void createImpl(Method method, Class eventClass, ClassVisitor cv) { - String ownerType = Type.getInternalName(method.getDeclaringClass()); - MethodVisitor mv = cv.visitMethod( - Opcodes.ACC_PUBLIC, - "execute", - Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Listener.class), Type.getType(Event.class)), - null, null - ); - mv.visitAnnotation(HIDDEN_FORM, true); - - Label label0 = new Label(); - Label label1 = new Label(); - Label label2 = new Label(); - mv.visitTryCatchBlock(label0, label1, label2, "java/lang/Throwable"); - Label label3 = new Label(); - Label label4 = new Label(); - // try { - mv.visitTryCatchBlock(label3, label4, label2, "java/lang/Throwable"); - // if (!(event instanceof TYPE)) - mv.visitLabel(label0); - mv.visitVarInsn(Opcodes.ALOAD, 2); - mv.visitTypeInsn(Opcodes.INSTANCEOF, Type.getInternalName(eventClass)); - mv.visitJumpInsn(Opcodes.IFNE, label3); - // return; - mv.visitLabel(label1); - mv.visitInsn(Opcodes.RETURN); - mv.visitLabel(label3); - mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - // ((TYPE) listener).(event); - // TYPE.(event); - int invokeCode; - if (Modifier.isStatic(method.getModifiers())) { - invokeCode = Opcodes.INVOKESTATIC; - } else if (method.getDeclaringClass().isInterface()) { - invokeCode = Opcodes.INVOKEINTERFACE; - } else { - invokeCode = Opcodes.INVOKEVIRTUAL; - } - if (invokeCode != Opcodes.INVOKESTATIC) { - mv.visitVarInsn(Opcodes.ALOAD, 1); - mv.visitTypeInsn(Opcodes.CHECKCAST, ownerType); - } - mv.visitVarInsn(Opcodes.ALOAD, 2); - mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(eventClass)); - mv.visitMethodInsn(invokeCode, ownerType, method.getName(), Type.getMethodDescriptor(method), invokeCode == Opcodes.INVOKEINTERFACE); - int retSize = Type.getType(method.getReturnType()).getSize(); - if (retSize > 0) { - mv.visitInsn(Opcodes.POP + retSize - 1); - } - mv.visitLabel(label4); - // } catch (Throwable t) { - Label label5 = new Label(); - mv.visitJumpInsn(Opcodes.GOTO, label5); - mv.visitLabel(label2); - mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/Throwable"}); - mv.visitVarInsn(Opcodes.ASTORE, 3); - // throw new EventException(t); - Label label6 = new Label(); - mv.visitLabel(label6); - mv.visitTypeInsn(Opcodes.NEW, "org/bukkit/event/EventException"); - mv.visitInsn(Opcodes.DUP); - mv.visitVarInsn(Opcodes.ALOAD, 3); - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "org/bukkit/event/EventException", "", "(Ljava/lang/Throwable;)V", false); - mv.visitInsn(Opcodes.ATHROW); - mv.visitLabel(label5); - mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - mv.visitInsn(Opcodes.RETURN); - // } - Label label7 = new Label(); - mv.visitLabel(label7); - mv.visitMaxs(-1, -1); - mv.visitEnd(); - } } diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/EventExecutorGenerator.java b/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/EventExecutorGenerator.java new file mode 100644 index 000000000..dff709f5f --- /dev/null +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/EventExecutorGenerator.java @@ -0,0 +1,151 @@ +package io.izzel.arclight.common.mod.util; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import io.izzel.arclight.api.Unsafe; +import org.bukkit.event.Event; +import org.bukkit.event.Listener; +import org.bukkit.plugin.EventExecutor; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * ASM generation of plugin {@link EventExecutor}s, extracted out of + * {@code JavaPluginLoaderMixin} so that Mixin never has to resolve the + * org.objectweb.asm.* types referenced here. On Java 25 the Mixin bytecode + * provider cannot load org.objectweb.asm.ClassVisitor metadata, which crashed + * the preprocessor when these methods lived inside the mixin. Keeping them in a + * normal class avoids that resolution entirely; the ASM classes still load and + * run fine at runtime. + */ +public final class EventExecutorGenerator { + + private EventExecutorGenerator() { + } + + private static final AtomicInteger COUNTER = new AtomicInteger(); + private static final Cache> EXECUTOR_CACHE = CacheBuilder.newBuilder() + .expireAfterAccess(1, TimeUnit.HOURS) + .build(); + private static final String HIDDEN_FORM = + Float.parseFloat(System.getProperty("java.class.version")) < 57 + ? "Ljava/lang/invoke/LambdaForm$Hidden;" + : "Ljdk/internal/vm/annotation/Hidden;"; + + @SuppressWarnings("unchecked") + public static Class createExecutor(Method method, Class eventClass) throws ExecutionException { + return EXECUTOR_CACHE.get(method, () -> { + ClassWriter cv = new ClassWriter(ClassWriter.COMPUTE_MAXS); + cv.visit(Opcodes.V1_8, + Opcodes.ACC_SUPER + Opcodes.ACC_SYNTHETIC + Opcodes.ACC_FINAL, + Type.getInternalName(method.getDeclaringClass()) + "$$arclight$" + COUNTER.getAndIncrement(), + null, + Type.getInternalName(Object.class), + new String[]{Type.getInternalName(EventExecutor.class)} + ); + cv.visitOuterClass(Type.getInternalName(method.getDeclaringClass()), null, null); + createConstructor(cv); + createImpl(method, eventClass, cv); + cv.visitEnd(); + return (Class) Unsafe.defineAnonymousClass(method.getDeclaringClass(), cv.toByteArray(), null); + }); + } + + private static void createConstructor(ClassVisitor cv) { + MethodVisitor mv = cv.visitMethod( + Opcodes.ACC_PRIVATE, + "", + "()V", + null, null); + mv.visitCode(); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), "", "()V", false); + mv.visitInsn(Opcodes.RETURN); + mv.visitMaxs(-1, -1); + mv.visitEnd(); + } + + private static void createImpl(Method method, Class eventClass, ClassVisitor cv) { + String ownerType = Type.getInternalName(method.getDeclaringClass()); + MethodVisitor mv = cv.visitMethod( + Opcodes.ACC_PUBLIC, + "execute", + Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Listener.class), Type.getType(Event.class)), + null, null + ); + mv.visitAnnotation(HIDDEN_FORM, true); + + Label label0 = new Label(); + Label label1 = new Label(); + Label label2 = new Label(); + mv.visitTryCatchBlock(label0, label1, label2, "java/lang/Throwable"); + Label label3 = new Label(); + Label label4 = new Label(); + // try { + mv.visitTryCatchBlock(label3, label4, label2, "java/lang/Throwable"); + // if (!(event instanceof TYPE)) + mv.visitLabel(label0); + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitTypeInsn(Opcodes.INSTANCEOF, Type.getInternalName(eventClass)); + mv.visitJumpInsn(Opcodes.IFNE, label3); + // return; + mv.visitLabel(label1); + mv.visitInsn(Opcodes.RETURN); + mv.visitLabel(label3); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + // ((TYPE) listener).(event); + // TYPE.(event); + int invokeCode; + if (Modifier.isStatic(method.getModifiers())) { + invokeCode = Opcodes.INVOKESTATIC; + } else if (method.getDeclaringClass().isInterface()) { + invokeCode = Opcodes.INVOKEINTERFACE; + } else { + invokeCode = Opcodes.INVOKEVIRTUAL; + } + if (invokeCode != Opcodes.INVOKESTATIC) { + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitTypeInsn(Opcodes.CHECKCAST, ownerType); + } + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(eventClass)); + mv.visitMethodInsn(invokeCode, ownerType, method.getName(), Type.getMethodDescriptor(method), invokeCode == Opcodes.INVOKEINTERFACE); + int retSize = Type.getType(method.getReturnType()).getSize(); + if (retSize > 0) { + mv.visitInsn(Opcodes.POP + retSize - 1); + } + mv.visitLabel(label4); + // } catch (Throwable t) { + Label label5 = new Label(); + mv.visitJumpInsn(Opcodes.GOTO, label5); + mv.visitLabel(label2); + mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/Throwable"}); + mv.visitVarInsn(Opcodes.ASTORE, 3); + // throw new EventException(t); + Label label6 = new Label(); + mv.visitLabel(label6); + mv.visitTypeInsn(Opcodes.NEW, "org/bukkit/event/EventException"); + mv.visitInsn(Opcodes.DUP); + mv.visitVarInsn(Opcodes.ALOAD, 3); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "org/bukkit/event/EventException", "", "(Ljava/lang/Throwable;)V", false); + mv.visitInsn(Opcodes.ATHROW); + mv.visitLabel(label5); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + mv.visitInsn(Opcodes.RETURN); + // } + Label label7 = new Label(); + mv.visitLabel(label7); + mv.visitMaxs(-1, -1); + mv.visitEnd(); + } +} diff --git a/arclight-forge/build.gradle b/arclight-forge/build.gradle index c6ea85cd4..1095af5a1 100644 --- a/arclight-forge/build.gradle +++ b/arclight-forge/build.gradle @@ -80,7 +80,9 @@ dependencies { } embed 'io.izzel.arclight:mixin-tools:1.0.1' embed "io.izzel:tools:$toolsVersion" - embed "io.izzel.arclight:arclight-api:$apiVersion" + // Java 25: patched arclight-api (Unsafe routed to jdk.internal.misc.Unsafe, StackWalker + // caller-class instead of SecurityManager). See ~/.arclight-proxy/arclight-api-fork. + embed files("libs/arclight-api-${apiVersion}-j25.jar") embed 'commons-lang:commons-lang:2.6@jar' embed(project(':i18n-config')) { transitive = false diff --git a/arclight-forge/libs/arclight-api-1.5.4-j25.jar b/arclight-forge/libs/arclight-api-1.5.4-j25.jar new file mode 100644 index 000000000..21b2f0bbf Binary files /dev/null and b/arclight-forge/libs/arclight-api-1.5.4-j25.jar differ diff --git a/arclight-forge/patches/README.md b/arclight-forge/patches/README.md new file mode 100644 index 000000000..ab8ea0a2a --- /dev/null +++ b/arclight-forge/patches/README.md @@ -0,0 +1,25 @@ +# Vendored patches — Java 25 runtime compatibility + +`io.izzel.arclight:arclight-api:1.5.4` is consumed as a binary dependency (its source is not +published). To run on Java 25 it needed patching, so the patched class source is kept here for +review/reproducibility and the rebuilt jar is vendored at +`arclight-forge/libs/arclight-api-1.5.4-j25.jar` (referenced by `arclight-forge/build.gradle`). + +## `arclight-api/io/izzel/arclight/api/Unsafe.java` +Patched from the decompiled 1.5.4 class. Changes vs upstream: +- `ensureClassInitialized` / `shouldBeInitialized` are routed to `jdk.internal.misc.Unsafe` + (the `sun.misc.Unsafe` versions were removed in JDK 22+), resolved through the trusted IMPL_LOOKUP. +- The caller-class provider always uses `StackWalker` (the `SecurityManager` constructor throws on + JDK 24+, JEP 486). +- The dead `sun.misc.Unsafe.defineAnonymousClass` fallback branch throws instead (gone since JDK 17). +- Restored the `(Object)` cast on the last `defineAnonymousClass` invokeExact argument (a cast the + decompiler had dropped; without it, plugin event-executor generation threw WrongMethodTypeException). + +### Rebuilding the jar +```bash +# compile only Unsafe.java against asm, then swap its classes into a copy of the original jar +javac -XDignore.symbol.file -cp asm-9.x.jar -d out \ + patches/arclight-api/io/izzel/arclight/api/Unsafe.java +# (extract original arclight-api-1.5.4.jar, replace io/izzel/arclight/api/Unsafe*.class with the +# freshly compiled ones, re-jar as arclight-api-1.5.4-j25.jar) +``` diff --git a/arclight-forge/patches/arclight-api/io/izzel/arclight/api/Unsafe.java b/arclight-forge/patches/arclight-api/io/izzel/arclight/api/Unsafe.java new file mode 100644 index 000000000..c334bc9f8 --- /dev/null +++ b/arclight-forge/patches/arclight-api/io/izzel/arclight/api/Unsafe.java @@ -0,0 +1,522 @@ +package io.izzel.arclight.api; + +import java.lang.StackWalker.Option; +import java.lang.StackWalker.StackFrame; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.security.ProtectionDomain; +import java.util.Arrays; +import java.util.Objects; +import org.objectweb.asm.ClassReader; + +public class Unsafe { + + private static final sun.misc.Unsafe unsafe; + private static final Lookup lookup; + private static final MethodHandle defineClass; + private static final MethodHandle H_DEF_CLASS; + private static final Unsafe.CallerClass INSTANCE; + // Java 25: ensureClassInitialized/shouldBeInitialized were removed from sun.misc.Unsafe; + // they are sourced from jdk.internal.misc.Unsafe via the trusted lookup instead. + private static final MethodHandle ensureInitMH; + private static final MethodHandle shouldInitMH; + + public static T getStatic(Class cl, String name) { + try { + ensureClassInitialized(cl); + Field field = cl.getDeclaredField(name); + Object materialByNameBase = staticFieldBase(field); + long materialByNameOffset = staticFieldOffset(field); + + return (T) getObject(materialByNameBase, materialByNameOffset); + } catch (Exception exception) { + return null; + } + } + + public static Lookup lookup() { + return Unsafe.lookup; + } + + public static sun.misc.Unsafe getUnsafe() { + return Unsafe.unsafe; + } + + public static int getInt(Object o, long l) { + return Unsafe.unsafe.getInt(o, l); + } + + public static void putInt(Object o, long l, int i) { + Unsafe.unsafe.putInt(o, l, i); + } + + public static Object getObject(Object o, long l) { + return Unsafe.unsafe.getObject(o, l); + } + + public static void putObject(Object o, long l, Object o1) { + Unsafe.unsafe.putObject(o, l, o1); + } + + public static boolean getBoolean(Object o, long l) { + return Unsafe.unsafe.getBoolean(o, l); + } + + public static void putBoolean(Object o, long l, boolean b) { + Unsafe.unsafe.putBoolean(o, l, b); + } + + public static byte getByte(Object o, long l) { + return Unsafe.unsafe.getByte(o, l); + } + + public static void putByte(Object o, long l, byte b) { + Unsafe.unsafe.putByte(o, l, b); + } + + public static short getShort(Object o, long l) { + return Unsafe.unsafe.getShort(o, l); + } + + public static void putShort(Object o, long l, short i) { + Unsafe.unsafe.putShort(o, l, i); + } + + public static char getChar(Object o, long l) { + return Unsafe.unsafe.getChar(o, l); + } + + public static void putChar(Object o, long l, char c) { + Unsafe.unsafe.putChar(o, l, c); + } + + public static long getLong(Object o, long l) { + return Unsafe.unsafe.getLong(o, l); + } + + public static void putLong(Object o, long l, long l1) { + Unsafe.unsafe.putLong(o, l, l1); + } + + public static float getFloat(Object o, long l) { + return Unsafe.unsafe.getFloat(o, l); + } + + public static void putFloat(Object o, long l, float v) { + Unsafe.unsafe.putFloat(o, l, v); + } + + public static double getDouble(Object o, long l) { + return Unsafe.unsafe.getDouble(o, l); + } + + public static void putDouble(Object o, long l, double v) { + Unsafe.unsafe.putDouble(o, l, v); + } + + public static byte getByte(long l) { + return Unsafe.unsafe.getByte(l); + } + + public static void putByte(long l, byte b) { + Unsafe.unsafe.putByte(l, b); + } + + public static short getShort(long l) { + return Unsafe.unsafe.getShort(l); + } + + public static void putShort(long l, short i) { + Unsafe.unsafe.putShort(l, i); + } + + public static char getChar(long l) { + return Unsafe.unsafe.getChar(l); + } + + public static void putChar(long l, char c) { + Unsafe.unsafe.putChar(l, c); + } + + public static int getInt(long l) { + return Unsafe.unsafe.getInt(l); + } + + public static void putInt(long l, int i) { + Unsafe.unsafe.putInt(l, i); + } + + public static long getLong(long l) { + return Unsafe.unsafe.getLong(l); + } + + public static void putLong(long l, long l1) { + Unsafe.unsafe.putLong(l, l1); + } + + public static float getFloat(long l) { + return Unsafe.unsafe.getFloat(l); + } + + public static void putFloat(long l, float v) { + Unsafe.unsafe.putFloat(l, v); + } + + public static double getDouble(long l) { + return Unsafe.unsafe.getDouble(l); + } + + public static void putDouble(long l, double v) { + Unsafe.unsafe.putDouble(l, v); + } + + public static long getAddress(long l) { + return Unsafe.unsafe.getAddress(l); + } + + public static void putAddress(long l, long l1) { + Unsafe.unsafe.putAddress(l, l1); + } + + public static long allocateMemory(long l) { + return Unsafe.unsafe.allocateMemory(l); + } + + public static long reallocateMemory(long l, long l1) { + return Unsafe.unsafe.reallocateMemory(l, l1); + } + + public static void setMemory(Object o, long l, long l1, byte b) { + Unsafe.unsafe.setMemory(o, l, l1, b); + } + + public static void setMemory(long l, long l1, byte b) { + Unsafe.unsafe.setMemory(l, l1, b); + } + + public static void copyMemory(Object o, long l, Object o1, long l1, long l2) { + Unsafe.unsafe.copyMemory(o, l, o1, l1, l2); + } + + public static void copyMemory(long l, long l1, long l2) { + Unsafe.unsafe.copyMemory(l, l1, l2); + } + + public static void freeMemory(long l) { + Unsafe.unsafe.freeMemory(l); + } + + public static long staticFieldOffset(Field field) { + return Unsafe.unsafe.staticFieldOffset(field); + } + + public static long objectFieldOffset(Field field) { + return Unsafe.unsafe.objectFieldOffset(field); + } + + public static Object staticFieldBase(Field field) { + return Unsafe.unsafe.staticFieldBase(field); + } + + public static boolean shouldBeInitialized(Class aClass) { + try { + return (boolean) Unsafe.shouldInitMH.invoke(aClass); + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + } + + public static void ensureClassInitialized(Class aClass) { + try { + Unsafe.ensureInitMH.invoke(aClass); + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + } + + public static int arrayBaseOffset(Class aClass) { + return Unsafe.unsafe.arrayBaseOffset(aClass); + } + + public static int arrayIndexScale(Class aClass) { + return Unsafe.unsafe.arrayIndexScale(aClass); + } + + public static int addressSize() { + return Unsafe.unsafe.addressSize(); + } + + public static int pageSize() { + return Unsafe.unsafe.pageSize(); + } + + public static Class defineClass(String s, byte[] bytes, int i, int i1, ClassLoader classLoader, ProtectionDomain protectionDomain) { + try { + return (Class) Unsafe.defineClass.invokeExact(s, bytes, i, i1, classLoader, protectionDomain); + } catch (Throwable throwable) { + throwException(throwable); + return null; + } + } + + public static Class defineAnonymousClass(Class aClass, byte[] bytes) { + return defineAnonymousClass(aClass, bytes, (Object[]) null); + } + + public static Class defineAnonymousClass(Class aClass, byte[] bytes, Object[] objects) { + if (Unsafe.H_DEF_CLASS != null) { + try { + return (Class) Unsafe.H_DEF_CLASS.invokeExact(aClass.getClassLoader(), aClass, (new ClassReader(bytes)).getClassName(), bytes, 0, bytes.length, aClass.getProtectionDomain(), false, 11, (Object) objects); + } catch (Throwable throwable) { + throwException(throwable); + return null; + } + } else { + // sun.misc.Unsafe.defineAnonymousClass was removed in JDK 17; on modern JDKs the + // H_DEF_CLASS path above is always used, so this branch is unreachable. + throw new UnsupportedOperationException("defineAnonymousClass is unavailable on this JDK"); + } + } + + public static Object allocateInstance(Class aClass) throws InstantiationException { + return Unsafe.unsafe.allocateInstance(aClass); + } + + public static void throwException(Throwable throwable) { + Unsafe.unsafe.throwException(throwable); + } + + public static boolean compareAndSwapObject(Object o, long l, Object o1, Object o2) { + return Unsafe.unsafe.compareAndSwapObject(o, l, o1, o2); + } + + public static boolean compareAndSwapInt(Object o, long l, int i, int i1) { + return Unsafe.unsafe.compareAndSwapInt(o, l, i, i1); + } + + public static boolean compareAndSwapLong(Object o, long l, long l1, long l2) { + return Unsafe.unsafe.compareAndSwapLong(o, l, l1, l2); + } + + public static Object getObjectVolatile(Object o, long l) { + return Unsafe.unsafe.getObjectVolatile(o, l); + } + + public static void putObjectVolatile(Object o, long l, Object o1) { + Unsafe.unsafe.putObjectVolatile(o, l, o1); + } + + public static int getIntVolatile(Object o, long l) { + return Unsafe.unsafe.getIntVolatile(o, l); + } + + public static void putIntVolatile(Object o, long l, int i) { + Unsafe.unsafe.putIntVolatile(o, l, i); + } + + public static boolean getBooleanVolatile(Object o, long l) { + return Unsafe.unsafe.getBooleanVolatile(o, l); + } + + public static void putBooleanVolatile(Object o, long l, boolean b) { + Unsafe.unsafe.putBooleanVolatile(o, l, b); + } + + public static byte getByteVolatile(Object o, long l) { + return Unsafe.unsafe.getByteVolatile(o, l); + } + + public static void putByteVolatile(Object o, long l, byte b) { + Unsafe.unsafe.putByteVolatile(o, l, b); + } + + public static short getShortVolatile(Object o, long l) { + return Unsafe.unsafe.getShortVolatile(o, l); + } + + public static void putShortVolatile(Object o, long l, short i) { + Unsafe.unsafe.putShortVolatile(o, l, i); + } + + public static char getCharVolatile(Object o, long l) { + return Unsafe.unsafe.getCharVolatile(o, l); + } + + public static void putCharVolatile(Object o, long l, char c) { + Unsafe.unsafe.putCharVolatile(o, l, c); + } + + public static long getLongVolatile(Object o, long l) { + return Unsafe.unsafe.getLongVolatile(o, l); + } + + public static void putLongVolatile(Object o, long l, long l1) { + Unsafe.unsafe.putLongVolatile(o, l, l1); + } + + public static float getFloatVolatile(Object o, long l) { + return Unsafe.unsafe.getFloatVolatile(o, l); + } + + public static void putFloatVolatile(Object o, long l, float v) { + Unsafe.unsafe.putFloatVolatile(o, l, v); + } + + public static double getDoubleVolatile(Object o, long l) { + return Unsafe.unsafe.getDoubleVolatile(o, l); + } + + public static void putDoubleVolatile(Object o, long l, double v) { + Unsafe.unsafe.putDoubleVolatile(o, l, v); + } + + public static void putOrderedObject(Object o, long l, Object o1) { + Unsafe.unsafe.putOrderedObject(o, l, o1); + } + + public static void putOrderedInt(Object o, long l, int i) { + Unsafe.unsafe.putOrderedInt(o, l, i); + } + + public static void putOrderedLong(Object o, long l, long l1) { + Unsafe.unsafe.putOrderedLong(o, l, l1); + } + + public static void unpark(Object o) { + Unsafe.unsafe.unpark(o); + } + + public static void park(boolean b, long l) { + Unsafe.unsafe.park(b, l); + } + + public static int getLoadAverage(double[] doubles, int i) { + return Unsafe.unsafe.getLoadAverage(doubles, i); + } + + public static int getAndAddInt(Object o, long l, int i) { + return Unsafe.unsafe.getAndAddInt(o, l, i); + } + + public static long getAndAddLong(Object o, long l, long l1) { + return Unsafe.unsafe.getAndAddLong(o, l, l1); + } + + public static int getAndSetInt(Object o, long l, int i) { + return Unsafe.unsafe.getAndSetInt(o, l, i); + } + + public static long getAndSetLong(Object o, long l, long l1) { + return Unsafe.unsafe.getAndSetLong(o, l, l1); + } + + public static Object getAndSetObject(Object o, long l, Object o1) { + return Unsafe.unsafe.getAndSetObject(o, l, o1); + } + + public static void loadFence() { + Unsafe.unsafe.loadFence(); + } + + public static void storeFence() { + Unsafe.unsafe.storeFence(); + } + + public static void fullFence() { + Unsafe.unsafe.fullFence(); + } + + public static Class getCallerClass() { + return Unsafe.INSTANCE.getCallerClass(); + } + + static { + try { + Field theUnsafe = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); + + theUnsafe.setAccessible(true); + unsafe = (sun.misc.Unsafe) theUnsafe.get((Object) null); + + // Lookup is already initialized by the JVM at this point; read the trusted IMPL_LOOKUP + // directly (the old ensureClassInitialized(Lookup.class) used a now-removed method). + Field field = Lookup.class.getDeclaredField("IMPL_LOOKUP"); + Object base = Unsafe.unsafe.staticFieldBase(field); + long offset = Unsafe.unsafe.staticFieldOffset(field); + + lookup = (Lookup) Unsafe.unsafe.getObject(base, offset); + + // jdk.internal.misc.Unsafe instance, read via sun.misc.Unsafe to bypass module checks. + Class jdkInternalUnsafe = Class.forName("jdk.internal.misc.Unsafe"); + Field internalUnsafeField = jdkInternalUnsafe.getDeclaredField("theUnsafe"); + Object internalUnsafe = Unsafe.unsafe.getObject(Unsafe.unsafe.staticFieldBase(internalUnsafeField), Unsafe.unsafe.staticFieldOffset(internalUnsafeField)); + + // ensureClassInitialized / shouldBeInitialized were removed from sun.misc.Unsafe (JDK 22+); + // route them to jdk.internal.misc.Unsafe through the trusted lookup. + ensureInitMH = Unsafe.lookup.unreflect(jdkInternalUnsafe.getMethod("ensureClassInitialized", Class.class)).bindTo(internalUnsafe); + shouldInitMH = Unsafe.lookup.unreflect(jdkInternalUnsafe.getMethod("shouldBeInitialized", Class.class)).bindTo(internalUnsafe); + + MethodHandle mh; + + try { + Method sunMisc = Unsafe.unsafe.getClass().getMethod("defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE, ClassLoader.class, ProtectionDomain.class); + + mh = Unsafe.lookup.unreflect(sunMisc).bindTo(Unsafe.unsafe); + } catch (Exception exception) { + Method internalDefineClass = jdkInternalUnsafe.getMethod("defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE, ClassLoader.class, ProtectionDomain.class); + + mh = Unsafe.lookup.unreflect(internalDefineClass).bindTo(internalUnsafe); + } + + defineClass = (MethodHandle) Objects.requireNonNull(mh); + } catch (Exception exception1) { + throw new RuntimeException(exception1); + } + + MethodHandle handle; + + try { + handle = lookup().findStatic(ClassLoader.class, "defineClass0", MethodType.methodType(Class.class, ClassLoader.class, Class.class, String.class, byte[].class, Integer.TYPE, Integer.TYPE, ProtectionDomain.class, Boolean.TYPE, Integer.TYPE, Object.class)); + } catch (Throwable throwable) { + try { + handle = MethodHandles.dropArguments(lookup().findVirtual(ClassLoader.class, "defineClassInternal", MethodType.methodType(Class.class, Class.class, String.class, byte[].class, ProtectionDomain.class, Boolean.TYPE, Integer.TYPE, Object.class)), 4, Arrays.asList(Integer.TYPE, Integer.TYPE)); + } catch (Throwable throwable1) { + handle = null; + } + } + + H_DEF_CLASS = handle; + + // StackWalker works on Java 9+ and avoids SecurityManager, whose constructor throws + // on JDK 24+ (the Security Manager was permanently disabled by JEP 486). + INSTANCE = new Unsafe.StackWalkerCallerClassimplements(); + } + + private interface CallerClass { + + Class getCallerClass(); + } + + private static class StackWalkerCallerClassimplements implements Unsafe.CallerClass { + + private final StackWalker walker; + + private StackWalkerCallerClassimplements() { + this.walker = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE); + } + + public Class getCallerClass() { + return (Class) this.walker.walk((sx) -> { + return ((StackFrame) sx.skip(3L).findFirst().orElseThrow()).getDeclaringClass(); + }); + } + + StackWalkerCallerClassimplements(Object x0) { + this(); + } + } + +} diff --git a/forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/ForgeInstaller.java b/forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/ForgeInstaller.java index 45b56f3dd..e9defc5f3 100644 --- a/forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/ForgeInstaller.java +++ b/forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/ForgeInstaller.java @@ -410,8 +410,15 @@ public static void addToPath(Path path) { Object ucp = Unsafe.getObject(loader, offset); if (ucp == null) { var cl = Class.forName("jdk.internal.loader.URLClassPath"); - var handle = Unsafe.lookup().findConstructor(cl, MethodType.methodType(void.class, URL[].class, AccessControlContext.class)); - ucp = handle.invoke(new URL[]{}, (AccessControlContext) null); + try { + // JDK 17-21: URLClassPath(URL[], AccessControlContext) + var handle = Unsafe.lookup().findConstructor(cl, MethodType.methodType(void.class, URL[].class, AccessControlContext.class)); + ucp = handle.invoke(new URL[]{}, (AccessControlContext) null); + } catch (NoSuchMethodException e) { + // JDK 24+ (JEP 486) dropped the AccessControlContext parameter + var handle = Unsafe.lookup().findConstructor(cl, MethodType.methodType(void.class, URL[].class)); + ucp = handle.invoke((Object) new URL[]{}); + } Unsafe.putObjectVolatile(loader, offset, ucp); } Method method = ucp.getClass().getDeclaredMethod("addURL", URL.class);