Java 24/25 support: recompute stale StackMapTables + JEP 486 launch#193
Java 24/25 support: recompute stale StackMapTables + JEP 486 launch#193GribanovIvan wants to merge 3 commits into
Conversation
…JDKs When a SpongePowered Mixin (e.g. via unimixins) rewrites a net.minecraft method, it recomputes only the operand-stack/local sizes (COMPUTE_MAXS) and leaves the method's StackMapTable stale. The split bytecode verifier rejects that with "VerifyError: Expecting a stackmap frame at branch target N" the moment the class is verified -- e.g. net.minecraft.world.gen.ChunkProviderServer.func_73153_a when Dynmap reflectively scans its fields during postInit -- so the server cannot start. Measured on the lwjgl3ify server path with a large GTNH-style pack: this happens on every modern JDK tested (21, 22, 23, 24, 25). Java 8 is unaffected because its verifier fails over to the old type-inference verifier for these class files. A bare server is unaffected; only packs whose Mixins touch net.minecraft hit it. Disabling the verifier (-Xverify:none) also lets the pack start but drops bytecode verification for every class. This is the surgical alternative: repair the affected frames and keep the verifier on. RFB's rfb-asm-safety plugin only makes getCommonSuperClass safe for transformers that already recompute frames; it does not force a recompute on a class whose stale frame was baked in by a COMPUTE_MAXS-only pass. Add AsmFrameSafetyTransformer, registered last on the LaunchClassLoader via CrucibleCoremodHook, which recomputes frames (COMPUTE_FRAMES) for net.minecraft.* classes at load time using a deobf-aware, never-throwing ClassWriter (SafeAsmClassWriter). Implementation note: ClassWriter(reader, flags) copies unchanged methods verbatim and skips COMPUTE_FRAMES; super(flags) without the reader forces a real recompute. Toggles: -Dcrucible.frameSafety=false disables it; -Dcrucible.frameSafety.scope=all (or a comma-separated prefix list) widens beyond net.minecraft. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three small changes that let Crucible launch on Java 24/25, complementing the StackMapTable fix: * FMLTweaker: on Java 24+ System.setSecurityManager() always throws UnsupportedOperationException (JEP 486). FMLTweaker only caught SecurityException, so it crashed at startup. Catch the new exception, log it, and continue -- the FML SecurityManager only trapped rogue System.exit() and SM replacement, neither essential. * java24args.txt: a Java 24+ launch-args file. It is java9args.txt without -Djava.security.manager=allow (which makes a Java 24+ JVM refuse to start) and the obsolete --illegal-access=warn (ignored since Java 17). * README: bump the advertised supported range from "Java 8-21" to "Java 8-25". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
May be we can move "-Dcrucible.frameSafety=false" and "-Dcrucible.frameSafety.scope=all" to Crucible config? |
Per review feedback (gravit0): expose the StackMapTable-recompute toggle and its class scope as Crucible.yml options instead of system-properties-only. - crucible.asm.frameSafety (bool, default true) — enable the recompute transformer. - crucible.asm.frameSafetyScope (string, default "net.minecraft.") — prefix filter; "all" recomputes everything. The system properties still work and OVERRIDE the config when set, so CI/launch scripts can flip them without editing the yml. Debug-only props (crucible.frameSafety.debug/.debugClass) stay system-property-only. CrucibleCoremodHook reads the config the same way the rest of the bootstrap package does (Lwjgl3ifyIntegration) — a direct CrucibleConfigs.configs reference; crucible.jar is the launch -jar so the class resolves. The transformer reads its scope the same way.
gravit0
left a comment
There was a problem hiding this comment.
I'd like to know what problem PR solves (specifically, working with ASM). I remember there was a problematic asjcore mod with a similar issue to the one described above: https://pastebin.com/TTGVr223
| @@ -9,5 +9,15 @@ | |||
| + sm.defman = System.getSecurityManager(); | |||
There was a problem hiding this comment.
On Java 8 this code will execute correctly, and on Java 9+ (lwjgl3ify) lwjgl3ify will redirect this call to itself and the problem will not occur.
|
|
||
| @SuppressWarnings("unchecked") | ||
| private void ensureRunsLast() { | ||
| final Field f = TRANSFORMERS_FIELD; |
There was a problem hiding this comment.
This is quite risky code with reflection. Even though we guarantee that the launchwrapper implementation is RFB and we can change this code if it changes, it's still a dirty hack.
| if (dbg) dump(transformedName + ".pre", basicClass); | ||
| try { | ||
| ClassReader reader = new ClassReader(basicClass); | ||
| SafeAsmClassWriter writer = new SafeAsmClassWriter(reader, ClassWriter.COMPUTE_FRAMES); |
There was a problem hiding this comment.
We can use com.gtnewhorizons.retrofuturabootstrap.asm.SafeAsmClassWriter. Why do we need to re-implement it here?
| // path: the full pack fails this way on every modern JDK tested (21-25); Java 8 is unaffected | ||
| // because its verifier fails over to the old type-inference verifier for these class files. | ||
| // -Dcrucible.frameSafety=true|false overrides the Crucible.yml value when set. | ||
| String frameSafetyProp = System.getProperty("crucible.frameSafety"); |
There was a problem hiding this comment.
Why do we need to use getProperty instead of CrucibleConfig?
| @@ -0,0 +1,36 @@ | |||
| -Dfile.encoding=UTF-8 | |||
There was a problem hiding this comment.
I think we should just remove "-Djava.security.manager=allow" from java9args.txt without creating a new file. lwjgl3ify will already patch the setSecurityManager call.
Java 24/25 support
Lets Crucible launch and run a Mixin-heavy modpack on Java 24/25. Verified by building this branch (Java 8) and booting a large GTNH-style pack (~62 mods, incl. unimixins + lwjgl3ify + Galacticraft + Dynmap) to
Doneon Oracle GraalVM 25 with the bytecode verifier on (0 VerifyErrors).Commit 1 — Recompute stale StackMapTables
A SpongePowered Mixin (via unimixins) rewrites a
net.minecraftmethod withCOMPUTE_MAXSonly, leaving its StackMapTable stale. The split verifier then rejects it withVerifyError: Expecting a stackmap frame at branch target N— e.g.ChunkProviderServer.func_73153_awhen Dynmap reflectively scans its fields in postInit — so the server can't start. Measured on every modern JDK tested (21–25); Java 8 is unaffected (its verifier fails over to the old type-inference verifier).-Xverify:nonealso works but drops verification for every class. This is the surgical alternative: a transformer (AsmFrameSafetyTransformer, registered last on the LaunchClassLoader viaCrucibleCoremodHook) force-recomputes frames fornet.minecraft.*with a deobf-aware, never-throwingSafeAsmClassWriter, keeping the verifier on. RFB'srfb-asm-safetyonly helps transformers that already recompute; it doesn't force a recompute on a class whose stale frame a COMPUTE_MAXS-only pass baked in.Toggles:
-Dcrucible.frameSafety=falseto disable;-Dcrucible.frameSafety.scope=all(or a prefix list) to widen beyondnet.minecraft.Commit 2 — Java 24+ launch plumbing
System.setSecurityManager()always throwsUnsupportedOperationException(JEP 486); FMLTweaker only caughtSecurityExceptionand crashed. Catch it, log, continue (the FML SM only trapped rogueSystem.exit()/SM-replacement).java9args.txtminus-Djava.security.manager=allow(makes a Java 24+ JVM refuse to start) and the obsolete--illegal-access=warn.8–21→8–25.🤖 Sorry for generating it with Claude Code, one day I shall learn java