diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 3a21b21..02bda2e 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -26,9 +26,7 @@ jobs: uses: actions/setup-java@v5 with: distribution: zulu - java-version: | - 8 - 25 + java-version: 8 - name: Set up Gradle uses: gradle/actions/setup-gradle@v5 diff --git a/README.md b/README.md index 3f62d5a..be11505 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,7 @@ An API for dynamically handling different functional parts in the one block spac Requirements: * The latest version of CCL (files.minecraftforge.net/CodeChickenLib/) * Forge -* JDK 8 and JDK 25. The mod targets Java 8, but a small set of helpers is compiled at Java 21 by the - `compileModernJava` task and downgraded to Java 8, so both toolchains must be resolvable to build. +* JDK 8. The mod compiles directly for Java 8. * A scala compatible IDE and project (For eclipse, download the scala eclipse plugin and right click on the project -> Add Scala Nature). Most of the codebase is Java now; the Java sources live under `src/main/scala` so they can be joint-compiled with the Scala files that remain. diff --git a/build.gradle b/build.gradle index cd64195..c4569cc 100644 --- a/build.gradle +++ b/build.gradle @@ -25,52 +25,7 @@ tasks.test { } } -// Scope modern syntax to these helpers; retained Scala and joint Java stay on Java 8. -apply plugin: xyz.wagyourtail.jvmdg.gradle.JVMDowngraderPlugin - -// Single source of truth for the modern-syntax helpers. These are compiled by compileModernJava -// and must be excluded from compileScala; keep one list, because a file excluded from the joint -// Scala compile but absent from this list is never compiled at all, and nothing reports it. -def modernJavaPaths = [ - 'codechicken/multipart/asm/StackAnalyserLogic.java', - 'codechicken/multipart/asm/JavaTraitRegistration.java', - 'codechicken/multipart/asm/ClassInfoLookup.java', - 'codechicken/multipart/asm/ScalaSignatureParser.java', - 'codechicken/microblock/HollowMicroblockTraitLogic.java', - 'codechicken/microblock/PostMicroblockClientLogic.java', - 'codechicken/microblock/HollowMicroblockClientLogic.java', - 'codechicken/microblock/TMicroOcclusionLogic.java', -] -def modernJavaSources = sourceSets.main.scala.matching { include modernJavaPaths } -def legacyScala = tasks.named('compileScala') { +tasks.named('compileScala') { // Zinc does not invalidate joint-compiled Java annotations when Tags.VERSION changes. scalaCompileOptions.force = true - // Scalac reads the Java declaration without asking Java 8 javac to compile its body. - exclude modernJavaPaths - scalaCompileOptions.additionalParameters = ['-sourcepath', file('src/main/scala').absolutePath] - inputs.files(modernJavaSources).withPropertyName('modernJavaDeclarations').withPathSensitivity(PathSensitivity.RELATIVE) } - -def compileModernJava = tasks.register('compileModernJava', JavaCompile) { - source(modernJavaSources) - classpath = sourceSets.main.compileClasspath + files( - tasks.named('compileJava').flatMap { it.destinationDirectory }, - legacyScala.flatMap { it.destinationDirectory }) - destinationDirectory = layout.buildDirectory.dir('classes/java/modern') - javaCompiler = javaToolchains.compilerFor { languageVersion = JavaLanguageVersion.of(25) } - options.release = 21 - options.encoding = 'UTF-8' -} - -def downgradeModernJava = tasks.register('downgradeModernJava', xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles) { - inputCollection = files(compileModernJava.flatMap { it.destinationDirectory }) - classpath = compileModernJava.get().classpath - downgradeTo = JavaVersion.VERSION_1_8 - multiReleaseOriginal = false - multiReleaseVersions = [] as Set - // The bundled plugin otherwise declares outputs only when inputs already exist. - outputs.dirs(outputMap.values()) -} - -sourceSets.main.output.classesDirs.from(downgradeModernJava.map { it.outputCollection }) -tasks.named('classes') { dependsOn(downgradeModernJava) } diff --git a/docs/migration/DIVERGENCES.md b/docs/migration/DIVERGENCES.md index e1eb46d..868cc70 100644 --- a/docs/migration/DIVERGENCES.md +++ b/docs/migration/DIVERGENCES.md @@ -205,14 +205,6 @@ These are accepted once for the migration, not repeated for every otherwise equi the retained getter and old Iguana reflection. Its name, type and visibility remain unchanged. - TickScheduler's private Scala scheduler types become package-private static nested Java classes, retaining their binary names. -- The modern-syntax helpers that have nested or anonymous classes carry `xyz.wagyourtail.jvmdg.j11.NestHost` and - `NestMembers` annotations on 23 packaged classes, recording nest relationships Java 8 class files cannot express. - These annotation types are deliberately not shipped and JVM Downgrader is not a runtime dependency: the packaged - classes contain no executable Downgrader API reference, and reflection skips an annotation whose type is absent - instead of throwing, so `getAnnotations()` on these classes returns an empty array on a Java 8 runtime. Forge and - FMP's own scanners read annotation tables through ASM without loading the types. Do not shade the Downgrader API - jar to resolve them; that would place FMP classes in a namespace it does not own and duplicate types other - installed mods already provide. ## Updating this ledger diff --git a/docs/migration/MAINTAINERS.md b/docs/migration/MAINTAINERS.md index 2d5c58e..52f9a10 100644 --- a/docs/migration/MAINTAINERS.md +++ b/docs/migration/MAINTAINERS.md @@ -10,29 +10,16 @@ These constraints explain the retained build and runtime machinery. Consumer mig modern-runtime policy spans Java 17–26; validate packaged releases on the selected supported runtime. Equal throughput across Java versions is not required. The deobfuscated Forge test runner uses Java 8; the current modern dedicated-server run tasks are not a substitute for packaged validation. -- Keep `enableModernJavaSyntax = false` while Scala 2.11.5 remains. The convention plugin's global JVM Downgrader - mode forces a modern toolchain that Scala cannot run on. With gtnhgradle 2.0.29, forcing Java 8 also conflicts - with the mode's multi-release version constraints. Revisit the supported global mode after Scala removal, - including an explicit stubs-provider decision; `gtnhlib` adds a runtime dependency and shading is rejected in - [DIVERGENCES.md](DIVERGENCES.md). -- Normal Java and joint Scala/Java compilation use Java 8. `modernJavaPaths` in `build.gradle` is the single list - that both excludes modern helpers from joint javac and includes them in `compileModernJava`. Scalac resolves - their declarations through `-sourcepath`; JDK 25 compiles their bodies with `--release 21` after Scala compilation. - Only `downgradeModernJava`'s Java 8 output joins main outputs, tests, Forge and packaged jars. -- Keep declaration inputs and downgrade output directories declared to Gradle. A modern-source exclusion without - a matching inclusion can silently omit a class. Helpers importing retained Scala models cannot simply move to - `src/main/java`: `compileJava` runs before those Scala types exist. Retire per-file routing when the dependency - cycle permits a source-set boundary, and remove the special routing after Scala removal. +- Keep `enableModernJavaSyntax = false` while Scala 2.11.5 remains. Normal Java and joint Scala/Java compilation + use Java 8. Helpers importing retained Scala models belong in the joint `src/main/scala` source set because + `compileJava` runs before those Scala types exist. - `scalaCompileOptions.force = true` prevents stale `Tags.VERSION` constants in joint-compiled Java `@Mod` annotations. Zinc otherwise misses those consumers when the generated constant changes. Verify all five mod annotations in dev/release artifacts against their artifact version; replace the guard only with equivalent coverage. -- Modernize bounded helpers only after checking declaration visibility, compile order, ABI and generated output. - Java 21 is the validated source ceiling. Records/sealed types are not automatic replacements for ABI-sensitive - models. New library APIs may need runtime stubs even when syntax downgrades successfully. - Keep registered trait inputs and compatibility facades conservative. Moving `RedstoneInteractions$` or `BlockMultipart` pulls transformer-sensitive dependencies; `RenderPartResolver` is needed by joint compilation - before the modern stage. Bootstrap and mixin source sets are not downgraded by this task. The explicit - `Microblock` cast in `PostMicroblockTraitLogic` is required by the retained Scala declaration. + before Java helpers that use it. The explicit `Microblock` cast in `PostMicroblockTraitLogic` is required by the + retained Scala declaration. - GTNHLib is compile-only here. Its fastutil compile classpath does not grant a runtime dependency. Declare that dependency explicitly before using fastutil in ordinary code; require profiling evidence for such a change. @@ -106,7 +93,7 @@ mechanical ports. Coverage percentages and signature counts alone are insufficie A Scala-to-Java replacement needs clean verification: Zinc can retain the removed Scala class and package it instead of exercising the new source. Stop the daemon if Windows holds build outputs open. Verify packaged -class version 52, unexpected downgrader API references and mod-version freshness, including after the release commit. +class version 52 and mod-version freshness, including after the release commit. JUnit reports are under `build/test-results/test/` and `run/server/junit-out/`; inspect counts, failures, errors and skips. The Forge task rejects missing/failed reports. Keep that CI gate required. Generated dumps are under diff --git a/gradle.properties b/gradle.properties index cdd0f14..1a7df6b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -39,34 +39,13 @@ remoteMappings = https\://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/co # `./gradlew runClient --username=AnotherPlayer`, or configuring this command in your IDE. developmentEnvironmentUserName = Developer -# Enables modern Java syntax support. Valid values: -# - false: No modern syntax, Java 8 only -# - jabel: Jabel syntax-only support, compiles to J8 bytecode -# - jvmDowngrader: Full modern Java via JVM Downgrader (syntax + stdlib APIs) -# - modern: Native modern Java bytecode, no downgrading +# Retained Scala and Java sources compile directly for Java 8. enableModernJavaSyntax = false # If set, ignores the above setting and compiles with the given toolchain. This may cause unexpected issues, # and should *not* be used in most situations. -1 disables this. # forceToolchainVersion = -1 -# Target JVM version for JVM Downgrader bytecode downgrading. -# Only used when enableModernJavaSyntax = jvmDowngrader -# downgradeTargetVersion = 8 - -# Comma-separated list of Java versions for multi-release jar support (JVM Downgrader only). -# Classes will be available in META-INF/versions/N/ for each version N in this list. -# Default: "21,25" (J25+ gets native classes, J21-24 gets partial downgrade, J8-20 gets full downgrade). -# jvmDowngraderMultiReleaseVersions = 21,25 - -# Specifies how JVM Downgrader API stubs are provided. Options: -# - shade: Shade minimized stubs into the jar -# - gtnhlib: GTNHLib provides stubs at runtime (adds version constraint) -# - external: Another dependency provides stubs (no constraint, no warning) -# - (empty): Warning reminding you to configure stubs -# Note: 'shade' option requires you to verify license compliance, see: https://github.com/unimined/JvmDowngrader/blob/main/LICENSE.md -# jvmDowngraderStubsProvider = - # Enables injecting missing generics into the decompiled source code for a better coding experience. # Turns most publicly visible List, Map, etc. into proper List, Map types. enableGenericInjection = false diff --git a/src/main/scala/codechicken/microblock/HollowMicroblockClientLogic.java b/src/main/scala/codechicken/microblock/HollowMicroblockClientLogic.java index 48341e8..10fad02 100644 --- a/src/main/scala/codechicken/microblock/HollowMicroblockClientLogic.java +++ b/src/main/scala/codechicken/microblock/HollowMicroblockClientLogic.java @@ -75,7 +75,8 @@ final class HollowMicroblockClientLogic { int slot = part.getSlot(); int internalMask = 0; switch (slot) { - case 0, 1 -> { + case 0: + case 1: if (face) internalMask = 0x3c; f.apply(pos, material, pass, new Cuboid6(d1, y1, d2, d2, y2, z2), 0x3b | internalMask); f.apply(pos, material, pass, new Cuboid6(d1, y1, z1, d2, y2, d1), 0x37 | internalMask); @@ -83,8 +84,9 @@ final class HollowMicroblockClientLogic { f.apply(pos, material, pass, new Cuboid6(x1, y1, d1, d1, y2, d2), sideMask & 0x13 | 0xc | internalMask); f.apply(pos, material, pass, new Cuboid6(x1, y1, d2, x2, y2, z2), sideMask & 0x3b | 4 | internalMask); f.apply(pos, material, pass, new Cuboid6(x1, y1, z1, x2, y2, d1), sideMask & 0x37 | 8 | internalMask); - } - case 2, 3 -> { + break; + case 2: + case 3: if (face) internalMask = 0x33; f.apply(pos, material, pass, new Cuboid6(d2, d1, z1, x2, d2, z2), 0x2f | internalMask); f.apply(pos, material, pass, new Cuboid6(x1, d1, z1, d1, d2, z2), 0x1f | internalMask); @@ -102,8 +104,9 @@ final class HollowMicroblockClientLogic { pass, new Cuboid6(x1, y1, z1, d1, y2, z2), sideMask & 0x1f | 0x20 | internalMask); - } - case 4, 5 -> { + break; + case 4: + case 5: if (face) internalMask = 0xf; f.apply(pos, material, pass, new Cuboid6(x1, d2, d1, x2, y2, d2), 0x3e | internalMask); f.apply(pos, material, pass, new Cuboid6(x1, y1, d1, x2, d1, d2), 0x3d | internalMask); @@ -111,8 +114,9 @@ final class HollowMicroblockClientLogic { f.apply(pos, material, pass, new Cuboid6(x1, d1, z1, x2, d2, d1), sideMask & 0x34 | 3 | internalMask); f.apply(pos, material, pass, new Cuboid6(x1, d2, z1, x2, y2, z2), sideMask & 0x3e | 1 | internalMask); f.apply(pos, material, pass, new Cuboid6(x1, y1, z1, x2, d1, z2), sideMask & 0x3d | 2 | internalMask); - } - default -> throw new MatchError(slot); + break; + default: + throw new MatchError(slot); } } diff --git a/src/main/scala/codechicken/microblock/HollowMicroblockTraitLogic.java b/src/main/scala/codechicken/microblock/HollowMicroblockTraitLogic.java index b5d1b97..10a41ca 100644 --- a/src/main/scala/codechicken/microblock/HollowMicroblockTraitLogic.java +++ b/src/main/scala/codechicken/microblock/HollowMicroblockTraitLogic.java @@ -42,8 +42,8 @@ final class HollowMicroblockTraitLogic { } // The second virtual lookup is observable when a part overrides tile(). TMultiPart center = ((TMultiPart) part).tile().partMap(6); - if (center instanceof ISidedHollowConnect connection) { - return connection.getHollowSize(part.getSlot()); + if (center instanceof ISidedHollowConnect) { + return ((ISidedHollowConnect) center).getHollowSize(part.getSlot()); } return 8; } @@ -60,15 +60,26 @@ final class HollowMicroblockTraitLogic { double z1 = c.min.z; double z2 = c.max.z; int slot = part.getSlot(); - Cuboid6[] boxes = switch (slot) { - case 0, 1 -> new Cuboid6[] { new Cuboid6(d2, y1, d1, x2, y2, d2), new Cuboid6(x1, y1, d1, d1, y2, d2), - new Cuboid6(x1, y1, d2, x2, y2, z2), new Cuboid6(x1, y1, z1, x2, y2, d1) }; - case 2, 3 -> new Cuboid6[] { new Cuboid6(d1, d2, z1, d2, y2, z2), new Cuboid6(d1, y1, z1, d2, d1, z2), - new Cuboid6(d2, y1, z1, x2, y2, z2), new Cuboid6(x1, y1, z1, d1, y2, z2) }; - case 4, 5 -> new Cuboid6[] { new Cuboid6(x1, d1, d2, x2, d2, z2), new Cuboid6(x1, d1, z1, x2, d2, d1), - new Cuboid6(x1, d2, z1, x2, y2, z2), new Cuboid6(x1, y1, z1, x2, d1, z2) }; - default -> throw new MatchError(slot); - }; + Cuboid6[] boxes; + switch (slot) { + case 0: + case 1: + boxes = new Cuboid6[] { new Cuboid6(d2, y1, d1, x2, y2, d2), new Cuboid6(x1, y1, d1, d1, y2, d2), + new Cuboid6(x1, y1, d2, x2, y2, z2), new Cuboid6(x1, y1, z1, x2, y2, d1) }; + break; + case 2: + case 3: + boxes = new Cuboid6[] { new Cuboid6(d1, d2, z1, d2, y2, z2), new Cuboid6(d1, y1, z1, d2, d1, z2), + new Cuboid6(d2, y1, z1, x2, y2, z2), new Cuboid6(x1, y1, z1, d1, y2, z2) }; + break; + case 4: + case 5: + boxes = new Cuboid6[] { new Cuboid6(x1, d1, d2, x2, d2, z2), new Cuboid6(x1, d1, z1, x2, d2, d1), + new Cuboid6(x1, d2, z1, x2, y2, z2), new Cuboid6(x1, y1, z1, x2, d1, z2) }; + break; + default: + throw new MatchError(slot); + } return JavaConversions.seqAsJavaList(sequence(boxes)); } diff --git a/src/main/scala/codechicken/microblock/PostMicroblockClientLogic.java b/src/main/scala/codechicken/microblock/PostMicroblockClientLogic.java index 33a58fe..46cccde 100644 --- a/src/main/scala/codechicken/microblock/PostMicroblockClientLogic.java +++ b/src/main/scala/codechicken/microblock/PostMicroblockClientLogic.java @@ -74,8 +74,8 @@ final class PostMicroblockClientLogic { @Override public BoxedUnit apply(TMultiPart other) { - if (other instanceof PostMicroblock post && !other.equals(part)) { - part.shrinkPost(post); + if (other instanceof PostMicroblock && !other.equals(part)) { + part.shrinkPost((PostMicroblock) other); } return BoxedUnit.UNIT; } diff --git a/src/main/scala/codechicken/microblock/TMicroOcclusionLogic.java b/src/main/scala/codechicken/microblock/TMicroOcclusionLogic.java index 9d908d2..623f800 100644 --- a/src/main/scala/codechicken/microblock/TMicroOcclusionLogic.java +++ b/src/main/scala/codechicken/microblock/TMicroOcclusionLogic.java @@ -9,9 +9,10 @@ final class TMicroOcclusionLogic { private TMicroOcclusionLogic() {} static boolean occlusionTest(TMicroOcclusion part, TMultiPart next) { - if (!(next instanceof TMicroOcclusion other)) { + if (!(next instanceof TMicroOcclusion)) { return true; } + TMicroOcclusion other = (TMicroOcclusion) next; int shape1 = MicroOcclusion$.MODULE$.shapePriority(part.getSlot()); int shape2 = MicroOcclusion$.MODULE$.shapePriority(other.getSlot()); if (other.getSize() + part.getSize() > 8) { diff --git a/src/main/scala/codechicken/multipart/asm/ClassInfoLookup.java b/src/main/scala/codechicken/multipart/asm/ClassInfoLookup.java index b3b0159..0fb0900 100644 --- a/src/main/scala/codechicken/multipart/asm/ClassInfoLookup.java +++ b/src/main/scala/codechicken/multipart/asm/ClassInfoLookup.java @@ -191,7 +191,7 @@ final class ClassInfoLookup { // Preserve the compiler's argument-count indexing, including its behavior for wide stack values. StackEntry receiver = stack.peek(Type.getType(call.desc).getArgumentTypes().length); - if (!(receiver instanceof Load load) || !(load.e() instanceof This)) return Option.empty(); + if (!(receiver instanceof Load) || !(((Load) receiver).e() instanceof This)) return Option.empty(); return ASMMixinCompiler$.MODULE$.getClassInfo(stack.owner().getInternalName()).superClass() .flatMap(new AbstractFunction1>() { diff --git a/src/main/scala/codechicken/multipart/asm/JavaTraitRegistration.java b/src/main/scala/codechicken/multipart/asm/JavaTraitRegistration.java index 0f31d00..a9d05e8 100644 --- a/src/main/scala/codechicken/multipart/asm/JavaTraitRegistration.java +++ b/src/main/scala/codechicken/multipart/asm/JavaTraitRegistration.java @@ -189,7 +189,8 @@ final class JavaTraitRegistration { InsnList instructions = method.instructions; AbstractInsnNode instruction = instructions.getFirst(); while (instruction != null) { - if (instruction instanceof FieldInsnNode field) { + if (instruction instanceof FieldInsnNode) { + FieldInsnNode field = (FieldInsnNode) instruction; if (instruction.getOpcode() == GETFIELD) instruction = replace( instructions, instruction, @@ -208,7 +209,8 @@ final class JavaTraitRegistration { fieldName(field.name) + "_$eq", "(" + field.desc + ")V", true)); - } else if (instruction instanceof MethodInsnNode call) { + } else if (instruction instanceof MethodInsnNode) { + MethodInsnNode call = (MethodInsnNode) instruction; if (instruction.getOpcode() == INVOKESPECIAL) { if (ASMMixinCompiler$.MODULE$.getSuper(call, stack).isDefined()) instruction = replace(instructions, instruction, superInstruction(call)); @@ -284,12 +286,14 @@ final class JavaTraitRegistration { StackAnalyser stack = new StackAnalyser(Type.getObjectType(input.name), source); AbstractInsnNode instruction = constructor.instructions.getFirst(); while (instruction != null) { - if (instruction.getOpcode() == INVOKESPECIAL && instruction instanceof MethodInsnNode call) { + if (instruction.getOpcode() == INVOKESPECIAL && instruction instanceof MethodInsnNode) { + MethodInsnNode call = (MethodInsnNode) instruction; if (Objects.equals(call.owner, input.superName) && Objects.equals(call.name, "")) { int argumentWidth = 0; for (Type argument : Type.getArgumentTypes(call.desc)) argumentWidth += argument.getSize(); StackAnalyser.StackEntry receiver = stack.peek(argumentWidth); - if (receiver instanceof StackAnalyser.Load load && load.e() instanceof StackAnalyser.This) { + if (receiver instanceof StackAnalyser.Load + && ((StackAnalyser.Load) receiver).e() instanceof StackAnalyser.This) { removeRange(constructor.instructions, receiver.insn(), instruction); return; } diff --git a/src/main/scala/codechicken/multipart/asm/ScalaSignatureParser.java b/src/main/scala/codechicken/multipart/asm/ScalaSignatureParser.java index 5f1af44..9125a7b 100644 --- a/src/main/scala/codechicken/multipart/asm/ScalaSignatureParser.java +++ b/src/main/scala/codechicken/multipart/asm/ScalaSignatureParser.java @@ -157,18 +157,22 @@ final class ScalaSignatureParser { Bytes bytes = entry.bytes(); ByteCodeReader reader = bytes.reader(); byte id = entry.id(); - return switch (id) { - case 1, 2 -> reader.readString(bytes.len()); - case 3 -> sig.NoSymbol().full(); - case 9, 10 -> { + switch (id) { + case 1: + case 2: + return reader.readString(bytes.len()); + case 3: + return sig.NoSymbol().full(); + case 9: + case 10: String name = sig.evalS(reader.readNat()); if (bytes.pos() + bytes.len() > reader.pos()) { name = sig.evalS(reader.readNat()) + "." + name; } - yield name; - } - default -> throw new MatchError(Byte.valueOf(id)); - }; + return name; + default: + throw new MatchError(Byte.valueOf(id)); + } } static List evalList(ScalaSignature sig, ByteCodeReader reader) { @@ -181,36 +185,61 @@ final class ScalaSignatureParser { static Object eval(ScalaSignature sig, int index, Object entry, ByteCodeReader reader, byte id) { // The Scala optimizer constructs case classes directly, without initializing their companions. - return switch (id) { - case 1, 2 -> sig.evalS(index); - case 3 -> sig.NoSymbol(); - case 6 -> sig.new ClassSymbol(sig.evalS(reader.readNat()), sig.evalT(reader.readNat()), reader.readNat(), - reader.readNat()); - case 7 -> sig.new ObjectSymbol(sig.evalS(reader.readNat()), sig.evalT(reader.readNat()), reader.readNat(), - reader.readNat()); - case 8 -> sig.new MethodSymbol(sig.evalS(reader.readNat()), sig.evalT(reader.readNat()), reader.readNat(), - reader.readNat()); - case 9, 10 -> sig.new ExternalSymbol(sig.evalS(index)); - // NoPrefixType is treated as NoType in the reference. - case 11, 12 -> sig.NoType(); - case 13 -> sig.new ThisType(sig.evalT(reader.readNat())); - case 14 -> sig.new SingleType(sig.evalT(reader.readNat()), sig.evalT(reader.readNat())); - // Retain the reference's parameterless interpretation of a bounded super type. - case 21, 48 -> sig.new ParameterlessType(sig.evalT(reader.readNat())); - case 25 -> sig.new BooleanLiteral(reader.readLong() != 0); - case 26 -> sig.new ByteLiteral((byte) reader.readLong()); - case 27 -> sig.new ShortLiteral((short) reader.readLong()); - case 28 -> sig.new CharLiteral((char) reader.readLong()); - case 29 -> sig.new IntLiteral((int) reader.readLong()); - case 30 -> sig.new LongLiteral(reader.readLong()); - case 31 -> sig.new FloatLiteral(Float.intBitsToFloat((int) reader.readLong())); - case 32 -> sig.new DoubleLiteral(Double.longBitsToDouble(reader.readLong())); - case 33 -> sig.new StringLiteral(sig.evalS(reader.readNat())); - case 34 -> sig.NullLiteral(); - case 35 -> sig.new TypeLiteral(sig.evalT(reader.readNat())); - case 36 -> sig.new EnumLiteral(sig.evalT(reader.readNat())); - default -> entry; - }; + switch (id) { + case 1: + case 2: + return sig.evalS(index); + case 3: + return sig.NoSymbol(); + case 6: + return sig.new ClassSymbol(sig.evalS(reader.readNat()), sig.evalT(reader.readNat()), reader.readNat(), + reader.readNat()); + case 7: + return sig.new ObjectSymbol(sig.evalS(reader.readNat()), sig.evalT(reader.readNat()), reader.readNat(), + reader.readNat()); + case 8: + return sig.new MethodSymbol(sig.evalS(reader.readNat()), sig.evalT(reader.readNat()), reader.readNat(), + reader.readNat()); + case 9: + case 10: + return sig.new ExternalSymbol(sig.evalS(index)); + case 11: + case 12: // NoPrefixType is treated as NoType in the reference. + return sig.NoType(); + case 13: + return sig.new ThisType(sig.evalT(reader.readNat())); + case 14: + return sig.new SingleType(sig.evalT(reader.readNat()), sig.evalT(reader.readNat())); + case 21: + case 48: // Retain the reference's parameterless interpretation of a bounded super type. + return sig.new ParameterlessType(sig.evalT(reader.readNat())); + case 25: + return sig.new BooleanLiteral(reader.readLong() != 0); + case 26: + return sig.new ByteLiteral((byte) reader.readLong()); + case 27: + return sig.new ShortLiteral((short) reader.readLong()); + case 28: + return sig.new CharLiteral((char) reader.readLong()); + case 29: + return sig.new IntLiteral((int) reader.readLong()); + case 30: + return sig.new LongLiteral(reader.readLong()); + case 31: + return sig.new FloatLiteral(Float.intBitsToFloat((int) reader.readLong())); + case 32: + return sig.new DoubleLiteral(Double.longBitsToDouble(reader.readLong())); + case 33: + return sig.new StringLiteral(sig.evalS(reader.readNat())); + case 34: + return sig.NullLiteral(); + case 35: + return sig.new TypeLiteral(sig.evalT(reader.readNat())); + case 36: + return sig.new EnumLiteral(sig.evalT(reader.readNat())); + default: + return entry; + } } static IndexedSeq collect(ScalaSignature sig, int id) { diff --git a/src/main/scala/codechicken/multipart/asm/StackAnalyserLogic.java b/src/main/scala/codechicken/multipart/asm/StackAnalyserLogic.java index 3630272..69857ab 100644 --- a/src/main/scala/codechicken/multipart/asm/StackAnalyserLogic.java +++ b/src/main/scala/codechicken/multipart/asm/StackAnalyserLogic.java @@ -56,20 +56,18 @@ final class StackAnalyserLogic { static Type constType(Const constant) { Object value = constant.c(); - return switch (value) { - case Byte ignored -> BYTE_TYPE; - case Short ignored -> SHORT_TYPE; - case Integer ignored -> INT_TYPE; - case Long ignored -> LONG_TYPE; - case Float ignored -> FLOAT_TYPE; - case Double ignored -> DOUBLE_TYPE; - case Character ignored -> CHAR_TYPE; - case Boolean ignored -> BOOLEAN_TYPE; - case String ignored -> getObjectType("java/lang/String"); - case null -> getObjectType("java/lang/Object"); - // The failure message rereads the virtual accessor after classification. - default -> throw new IllegalArgumentException("Unknown const " + constant.c()); - }; + if (value instanceof Byte) return BYTE_TYPE; + if (value instanceof Short) return SHORT_TYPE; + if (value instanceof Integer) return INT_TYPE; + if (value instanceof Long) return LONG_TYPE; + if (value instanceof Float) return FLOAT_TYPE; + if (value instanceof Double) return DOUBLE_TYPE; + if (value instanceof Character) return CHAR_TYPE; + if (value instanceof Boolean) return BOOLEAN_TYPE; + if (value instanceof String) return getObjectType("java/lang/String"); + if (value == null) return getObjectType("java/lang/Object"); + // The failure message rereads the virtual accessor after classification. + throw new IllegalArgumentException("Unknown const " + constant.c()); } static void initialize(StackAnalyser a, Type owner, MethodNode method, @@ -126,167 +124,256 @@ final class StackAnalyserLogic { } static void visitInsn(StackAnalyser a, AbstractInsnNode insn) { - switch (insn) { - case InsnNode ignored -> { - int op = insn.getOpcode(); - switch (op) { - case ACONST_NULL -> a.push(new Const(null, insn)); - case ICONST_M1 -> a.push(new Const(-1, insn)); - case ICONST_0 -> a.push(new Const(0, insn)); - case ICONST_1 -> a.push(new Const(1, insn)); - case ICONST_2 -> a.push(new Const(2, insn)); - case ICONST_3 -> a.push(new Const(3, insn)); - case ICONST_4 -> a.push(new Const(4, insn)); - case ICONST_5 -> a.push(new Const(5, insn)); - case LCONST_0 -> a.push(new Const(0L, insn)); - case LCONST_1 -> a.push(new Const(1L, insn)); - case FCONST_0 -> a.push(new Const(0f, insn)); - case FCONST_1 -> a.push(new Const(1f, insn)); - case FCONST_2 -> a.push(new Const(2f, insn)); - case DCONST_0 -> a.push(new Const(0d, insn)); - case DCONST_1 -> a.push(new Const(1d, insn)); - case POP -> rawPop(a); - case POP2 -> { - rawPop(a); - rawPop(a); - } - case DUP -> a.push(peek(a)); - case DUP_X1 -> a.insert(2, peek(a)); - case DUP_X2 -> a.insert(3, peek(a)); - case DUP2 -> { - a.push(a.peek(1)); - a.push(a.peek(1)); - } - case DUP2_X1 -> { - a.insert(3, a.peek(1)); - a.insert(3, peek(a)); - } - case DUP2_X2 -> { - a.insert(4, a.peek(1)); - a.insert(4, peek(a)); - } - case SWAP -> a.push(a.pop(1)); - // Preserve the reference's inferred types, including conversions to int reporting double. - case L2I, F2I, D2I -> a.push(new PrimitiveCast(pop(a), DOUBLE_TYPE, insn)); - case I2L, F2L, D2L -> a.push(new PrimitiveCast(pop(a), LONG_TYPE, insn)); - case I2F, L2F, D2F -> a.push(new PrimitiveCast(pop(a), FLOAT_TYPE, insn)); - case I2D, L2D, F2D -> a.push(new PrimitiveCast(pop(a), DOUBLE_TYPE, insn)); - case I2B -> a.push(new PrimitiveCast(pop(a), BYTE_TYPE, insn)); - case I2C -> a.push(new PrimitiveCast(pop(a), CHAR_TYPE, insn)); - case I2S -> a.push(new PrimitiveCast(pop(a), SHORT_TYPE, insn)); - case ARRAYLENGTH -> a.push(new ArrayLength(pop(a), insn)); - case ATHROW, MONITORENTER, MONITOREXIT -> pop(a); - default -> { - if (op >= IALOAD && op <= SALOAD) { - a.push(new ArrayLoad(pop(a), pop(a), insn)); - } else if (op >= IASTORE && op <= SASTORE) { - pop(a); - pop(a); - pop(a); - } else if (op >= IADD && op <= DREM || op >= ISHL && op <= LXOR || op >= LCMP && op <= DCMPG) { - a.push(new BinaryOp(op, pop(a), pop(a), insn)); - } else if (op >= INEG && op <= DNEG) { - a.push(new UnaryOp(op, pop(a), insn)); - } else if (op >= IRETURN && op <= ARETURN) { - pop(a); - } - } - } - } - case IntInsnNode intInsn -> { - switch (insn.getOpcode()) { - case BIPUSH -> a.push(new Const((byte) intInsn.operand, insn)); - case SIPUSH -> a.push(new Const((short) intInsn.operand, insn)); - default -> throw new MatchError(insn.getOpcode()); - } - } - case LdcInsnNode ldc -> { - if (insn.getOpcode() != LDC) throw new MatchError(insn.getOpcode()); - a.push(new Const(ldc.cst, insn)); - } - case VarInsnNode varInsn -> { - int op = insn.getOpcode(); - if (op >= ILOAD && op <= ALOAD) { - a.push(new Load(a.locals().apply(varInsn.var), insn)); - } else if (op >= ISTORE && op <= ASTORE) { - a.setL(varInsn.var, new Store(pop(a), insn)); - } else { - throw new MatchError(op); - } - } - case IincInsnNode incInsn -> { - if (insn.getOpcode() != IINC) throw new MatchError(insn.getOpcode()); - a.setL( - incInsn.var, - new Store( - new BinaryOp( - IINC, - new Const(incInsn.incr, insn), - new Load(a.locals().apply(incInsn.var), insn), - insn), - insn)); - } - case JumpInsnNode ignored -> { - int op = insn.getOpcode(); - if (op >= IFEQ && op <= IFLE || op == IFNULL || op == IFNONNULL) { + if (insn instanceof InsnNode) { + int op = insn.getOpcode(); + switch (op) { + case ACONST_NULL: + a.push(new Const(null, insn)); + break; + case ICONST_M1: + a.push(new Const(-1, insn)); + break; + case ICONST_0: + a.push(new Const(0, insn)); + break; + case ICONST_1: + a.push(new Const(1, insn)); + break; + case ICONST_2: + a.push(new Const(2, insn)); + break; + case ICONST_3: + a.push(new Const(3, insn)); + break; + case ICONST_4: + a.push(new Const(4, insn)); + break; + case ICONST_5: + a.push(new Const(5, insn)); + break; + case LCONST_0: + a.push(new Const(0L, insn)); + break; + case LCONST_1: + a.push(new Const(1L, insn)); + break; + case FCONST_0: + a.push(new Const(0f, insn)); + break; + case FCONST_1: + a.push(new Const(1f, insn)); + break; + case FCONST_2: + a.push(new Const(2f, insn)); + break; + case DCONST_0: + a.push(new Const(0d, insn)); + break; + case DCONST_1: + a.push(new Const(1d, insn)); + break; + case POP: + rawPop(a); + break; + case POP2: + rawPop(a); + rawPop(a); + break; + case DUP: + a.push(peek(a)); + break; + case DUP_X1: + a.insert(2, peek(a)); + break; + case DUP_X2: + a.insert(3, peek(a)); + break; + case DUP2: + a.push(a.peek(1)); + a.push(a.peek(1)); + break; + case DUP2_X1: + a.insert(3, a.peek(1)); + a.insert(3, peek(a)); + break; + case DUP2_X2: + a.insert(4, a.peek(1)); + a.insert(4, peek(a)); + break; + case SWAP: + a.push(a.pop(1)); + break; + // Preserve the reference's inferred types, including conversions to int reporting double. + case L2I: + case F2I: + case D2I: + a.push(new PrimitiveCast(pop(a), DOUBLE_TYPE, insn)); + break; + case I2L: + case F2L: + case D2L: + a.push(new PrimitiveCast(pop(a), LONG_TYPE, insn)); + break; + case I2F: + case L2F: + case D2F: + a.push(new PrimitiveCast(pop(a), FLOAT_TYPE, insn)); + break; + case I2D: + case L2D: + case F2D: + a.push(new PrimitiveCast(pop(a), DOUBLE_TYPE, insn)); + break; + case I2B: + a.push(new PrimitiveCast(pop(a), BYTE_TYPE, insn)); + break; + case I2C: + a.push(new PrimitiveCast(pop(a), CHAR_TYPE, insn)); + break; + case I2S: + a.push(new PrimitiveCast(pop(a), SHORT_TYPE, insn)); + break; + case ARRAYLENGTH: + a.push(new ArrayLength(pop(a), insn)); + break; + case ATHROW: + case MONITORENTER: + case MONITOREXIT: pop(a); - } else if (op >= IF_ICMPEQ && op <= IF_ACMPNE) { - pop(a); - pop(a); - } else if (op == JSR) { - a.push(new ReturnAddress(insn)); - } else if (op != GOTO) { - throw new MatchError(op); - } - } - case TableSwitchInsnNode ignored -> pop(a); - case LookupSwitchInsnNode ignored -> pop(a); - case FieldInsnNode field -> { - switch (insn.getOpcode()) { - case GETSTATIC -> a.push(new GetField(null, field, insn)); - case PUTSTATIC -> pop(a); - case GETFIELD -> a.push(new GetField(pop(a), field, insn)); - case PUTFIELD -> { + break; + default: + if (op >= IALOAD && op <= SALOAD) { + a.push(new ArrayLoad(pop(a), pop(a), insn)); + } else if (op >= IASTORE && op <= SASTORE) { pop(a); pop(a); + pop(a); + } else if (op >= IADD && op <= DREM || op >= ISHL && op <= LXOR || op >= LCMP && op <= DCMPG) { + a.push(new BinaryOp(op, pop(a), pop(a), insn)); + } else if (op >= INEG && op <= DNEG) { + a.push(new UnaryOp(op, pop(a), insn)); + } else if (op >= IRETURN && op <= ARETURN) { + pop(a); } - default -> throw new MatchError(insn.getOpcode()); - } } - case MethodInsnNode method -> { - switch (insn.getOpcode()) { - case INVOKEVIRTUAL, INVOKESPECIAL, INVOKEINTERFACE -> - a.push(new Invoke(insn.getOpcode(), a.popArgs(method.desc), pop(a), method, insn)); - case INVOKESTATIC -> - a.push(new Invoke(insn.getOpcode(), a.popArgs(method.desc), null, method, insn)); - default -> throw new MatchError(insn.getOpcode()); - } + } else if (insn instanceof IntInsnNode) { + IntInsnNode intInsn = (IntInsnNode) insn; + switch (insn.getOpcode()) { + case BIPUSH: + a.push(new Const((byte) intInsn.operand, insn)); + break; + case SIPUSH: + a.push(new Const((short) intInsn.operand, insn)); + break; + default: + throw new MatchError(insn.getOpcode()); } - case TypeInsnNode typeInsn -> { - switch (insn.getOpcode()) { - case NEW -> a.push(new New(getObjectType(typeInsn.desc), insn)); - case NEWARRAY -> a.push(new NewArray(pop(a), getObjectType(typeInsn.desc), insn)); - case ANEWARRAY -> a.push(new NewArray(pop(a), getObjectType("[" + typeInsn.desc), insn)); - case CHECKCAST -> a.push(new Cast(pop(a), getObjectType(typeInsn.desc), insn)); - case INSTANCEOF -> a.push(new UnaryOp(INSTANCEOF, pop(a), insn)); - default -> throw new MatchError(insn.getOpcode()); - } + } else if (insn instanceof LdcInsnNode) { + if (insn.getOpcode() != LDC) throw new MatchError(insn.getOpcode()); + a.push(new Const(((LdcInsnNode) insn).cst, insn)); + } else if (insn instanceof VarInsnNode) { + VarInsnNode varInsn = (VarInsnNode) insn; + int op = insn.getOpcode(); + if (op >= ILOAD && op <= ALOAD) { + a.push(new Load(a.locals().apply(varInsn.var), insn)); + } else if (op >= ISTORE && op <= ASTORE) { + a.setL(varInsn.var, new Store(pop(a), insn)); + } else { + throw new MatchError(op); } - case MultiANewArrayInsnNode multi -> { - StackEntry[] sizes = new StackEntry[multi.dims]; - for (int i = 0; i < sizes.length; i++) sizes[i] = pop(a); - a.push(new NewMultiArray(sizes, getType(multi.desc), insn)); + } else if (insn instanceof IincInsnNode) { + IincInsnNode incInsn = (IincInsnNode) insn; + if (insn.getOpcode() != IINC) throw new MatchError(insn.getOpcode()); + a.setL( + incInsn.var, + new Store( + new BinaryOp( + IINC, + new Const(incInsn.incr, insn), + new Load(a.locals().apply(incInsn.var), insn), + insn), + insn)); + } else if (insn instanceof JumpInsnNode) { + int op = insn.getOpcode(); + if (op >= IFEQ && op <= IFLE || op == IFNULL || op == IFNONNULL) { + pop(a); + } else if (op >= IF_ICMPEQ && op <= IF_ACMPNE) { + pop(a); + pop(a); + } else if (op == JSR) { + a.push(new ReturnAddress(insn)); + } else if (op != GOTO) { + throw new MatchError(op); } - case LabelNode label -> { - Option handler = a.codechicken$multipart$asm$StackAnalyser$$catchHandlers() - .get(label); - if (handler instanceof Some) { - a.push(new CaughtException(Type.getType(((Some) handler).x().type), insn)); - } else if (!None$.MODULE$.equals(handler)) { - throw new MatchError(handler); - } + } else if (insn instanceof TableSwitchInsnNode || insn instanceof LookupSwitchInsnNode) { + pop(a); + } else if (insn instanceof FieldInsnNode) { + FieldInsnNode field = (FieldInsnNode) insn; + switch (insn.getOpcode()) { + case GETSTATIC: + a.push(new GetField(null, field, insn)); + break; + case PUTSTATIC: + pop(a); + break; + case GETFIELD: + a.push(new GetField(pop(a), field, insn)); + break; + case PUTFIELD: + pop(a); + pop(a); + break; + default: + throw new MatchError(insn.getOpcode()); + } + } else if (insn instanceof MethodInsnNode) { + MethodInsnNode method = (MethodInsnNode) insn; + switch (insn.getOpcode()) { + case INVOKEVIRTUAL: + case INVOKESPECIAL: + case INVOKEINTERFACE: + a.push(new Invoke(insn.getOpcode(), a.popArgs(method.desc), pop(a), method, insn)); + break; + case INVOKESTATIC: + a.push(new Invoke(insn.getOpcode(), a.popArgs(method.desc), null, method, insn)); + break; + default: + throw new MatchError(insn.getOpcode()); + } + } else if (insn instanceof TypeInsnNode) { + TypeInsnNode typeInsn = (TypeInsnNode) insn; + switch (insn.getOpcode()) { + case NEW: + a.push(new New(getObjectType(typeInsn.desc), insn)); + break; + case NEWARRAY: + a.push(new NewArray(pop(a), getObjectType(typeInsn.desc), insn)); + break; + case ANEWARRAY: + a.push(new NewArray(pop(a), getObjectType("[" + typeInsn.desc), insn)); + break; + case CHECKCAST: + a.push(new Cast(pop(a), getObjectType(typeInsn.desc), insn)); + break; + case INSTANCEOF: + a.push(new UnaryOp(INSTANCEOF, pop(a), insn)); + break; + default: + throw new MatchError(insn.getOpcode()); + } + } else if (insn instanceof MultiANewArrayInsnNode) { + MultiANewArrayInsnNode multi = (MultiANewArrayInsnNode) insn; + StackEntry[] sizes = new StackEntry[multi.dims]; + for (int i = 0; i < sizes.length; i++) sizes[i] = pop(a); + a.push(new NewMultiArray(sizes, getType(multi.desc), insn)); + } else if (insn instanceof LabelNode) { + Option handler = a.codechicken$multipart$asm$StackAnalyser$$catchHandlers() + .get((LabelNode) insn); + if (handler instanceof Some) { + a.push(new CaughtException(Type.getType(((Some) handler).x().type), insn)); + } else if (!None$.MODULE$.equals(handler)) { + throw new MatchError(handler); } - case null, default -> {} } } }