diff --git a/gradle/test.libs.versions.toml b/gradle/test.libs.versions.toml index b609e5c2..6a854144 100644 --- a/gradle/test.libs.versions.toml +++ b/gradle/test.libs.versions.toml @@ -6,7 +6,7 @@ mockito = "5.14.2" java-debug = "0.52.0" mixin = "0.15.3+mixin.0.8.7" -gradle-nightly = "8.13-20250125001926+0000" +gradle-nightly = "8.14-20250208001853+0000" fabric-loader = "0.16.9" [libraries] diff --git a/src/main/java/net/fabricmc/loom/decompilers/cache/ClassEntry.java b/src/main/java/net/fabricmc/loom/decompilers/cache/ClassEntry.java index 666ae2c2..4fe53faa 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/cache/ClassEntry.java +++ b/src/main/java/net/fabricmc/loom/decompilers/cache/ClassEntry.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2024 FabricMC + * Copyright (c) 2024-2025 FabricMC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -45,6 +45,34 @@ import net.fabricmc.loom.util.Checksum; public record ClassEntry(String name, List innerClasses, List superClasses) { private static final Logger LOGGER = LoggerFactory.getLogger(ClassEntry.class); + public ClassEntry { + if (!name.endsWith(".class")) { + throw new IllegalArgumentException("Class name must end with '.class': " + name); + } + + if (!name.contains("/")) { + throw new IllegalArgumentException("Class name must be in a package: " + name); + } + + String className = name.replace(".class", ""); + + for (String innerClass : innerClasses) { + if (!innerClass.endsWith(".class")) { + throw new IllegalArgumentException("Inner class name must end with '.class': " + name); + } + + if (!innerClass.startsWith(className)) { + throw new IllegalArgumentException("Inner class (" + innerClass + ") does not have the parent class name as a prefix: " + name); + } + } + + for (String superClass : superClasses) { + if (!superClass.endsWith(".class")) { + throw new IllegalArgumentException("Super class name must end with '.class': " + superClass); + } + } + } + /** * Copy the class and its inner classes to the target root. * @param sourceRoot The root of the source jar @@ -55,13 +83,18 @@ public record ClassEntry(String name, List innerClasses, List su public void copyTo(Path sourceRoot, Path targetRoot) throws IOException { Path targetPath = targetRoot.resolve(name); Files.createDirectories(targetPath.getParent()); - Files.copy(sourceRoot.resolve(name), targetPath); + copy(sourceRoot.resolve(name), targetPath); for (String innerClass : innerClasses) { - Files.copy(sourceRoot.resolve(innerClass), targetRoot.resolve(innerClass)); + copy(sourceRoot.resolve(innerClass), targetRoot.resolve(innerClass)); } } + private void copy(Path source, Path target) throws IOException { + LOGGER.debug("Copying class entry `{}` from `{}` to `{}`", name, source, target); + Files.copy(source, target); + } + /** * Hash the class and its inner classes using sha256. * @param root The root of the jar @@ -95,7 +128,7 @@ public record ClassEntry(String name, List innerClasses, List su joiner.add(selfHash); for (String superClass : superClasses) { - final String superHash = hashes.get(superClass + ".class"); + final String superHash = hashes.get(superClass); if (superHash != null) { joiner.add(superHash); diff --git a/src/main/java/net/fabricmc/loom/decompilers/cache/JarWalker.java b/src/main/java/net/fabricmc/loom/decompilers/cache/JarWalker.java index a3931281..ad2e16b5 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/cache/JarWalker.java +++ b/src/main/java/net/fabricmc/loom/decompilers/cache/JarWalker.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2024 FabricMC + * Copyright (c) 2024-2025 FabricMC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -194,11 +194,14 @@ public final class JarWalker { List parentClasses = new ArrayList<>(); String superName = reader.getSuperName(); - if (superName != null) { - parentClasses.add(superName); + if (superName != null && !superName.equals("java/lang/Object")) { + parentClasses.add(superName + ".class"); + } + + for (String iface : reader.getInterfaces()) { + parentClasses.add(iface + ".class"); } - Collections.addAll(parentClasses, reader.getInterfaces()); return Collections.unmodifiableList(parentClasses); } catch (IOException e) { throw new UncheckedIOException("Failed to read class file: " + classFile, e); diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/cache/ClassEntryTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/cache/ClassEntryTest.groovy new file mode 100644 index 00000000..b766ecef --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/cache/ClassEntryTest.groovy @@ -0,0 +1,63 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.test.unit.cache + +import spock.lang.Specification + +import net.fabricmc.loom.decompilers.cache.ClassEntry + +class ClassEntryTest extends Specification { + def "valid class entry"() { + when: + def classEntry = new ClassEntry(name, innerClasses, superClasses) + then: + // Just make sure the constructor doesn't throw an exception + classEntry != null + where: + name | innerClasses | superClasses + "net/fabricmc/Test.class" | [] | [] + "net/fabricmc/Test.class" | [ + "net/fabricmc/Test\$Inner.class" + ] | ["java/lang/List.class"] + } + + def "invalid class entry"() { + when: + new ClassEntry(name, innerClasses, superClasses) + then: + thrown IllegalArgumentException + where: + name | innerClasses | superClasses + "net/fabricmc/Test" | [] | [] + "net/fabricmc/Test.class" | ["net/fabricmc/Test\$Inner"] | ["java/lang/List.class"] + "net/fabricmc/Test.class" | [ + "net/fabricmc/Test\$Inner.class" + ] | ["java/lang/List"] + "net/fabricmc/Test.class" | ["net/Test\$Inner.class"] | ["java/lang/List.class"] + "net/fabricmc/Test.class" | [ + "net/fabricmc/Bar\$Inner.class" + ] | [] + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/cache/JarWalkerTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/cache/JarWalkerTest.groovy index ef6db7ec..edc829fc 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/cache/JarWalkerTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/cache/JarWalkerTest.groovy @@ -123,7 +123,7 @@ class JarWalkerTest extends Specification { classes.size() == 1 classes[0].name() == "net/fabricmc/Example.class" classes[0].innerClasses() == [] - classes[0].superClasses() == ["java/lang/Runnable"] + classes[0].superClasses() == ["java/lang/Runnable.class"] } def "inner classes"() { @@ -146,8 +146,8 @@ class JarWalkerTest extends Specification { "net/fabricmc/other/Test\$Inner.class" ] classes[0].superClasses() == [ - "java/lang/Runnable", - "net/fabricmc/other/Super" + "java/lang/Runnable.class", + "net/fabricmc/other/Super.class" ] }