diff --git a/adapters/fabric/build.gradle b/adapters/fabric/build.gradle index 2d9b10165..8f9742094 100644 --- a/adapters/fabric/build.gradle +++ b/adapters/fabric/build.gradle @@ -313,6 +313,8 @@ tasks.named('shadowJar', ShadowJar).configure { // The Bukkit platform binding cannot load without org.bukkit on the classpath, which no mod // loader has. Shipping it only adds dead classes that reference a missing package. exclude('art/arcane/iris/platform/bukkit/**') + exclude('art/arcane/iris/platform/bootstrap/SlimJar*.class') + exclude('art/arcane/iris/platform/bootstrap/NativeRuntimeLibraries*.class') duplicatesStrategy = DuplicatesStrategy.EXCLUDE filesMatching('META-INF/services/**') { FileCopyDetails entry -> entry.duplicatesStrategy = DuplicatesStrategy.INCLUDE diff --git a/adapters/forge/build.gradle b/adapters/forge/build.gradle index 607b5afe7..ebba1ec23 100644 --- a/adapters/forge/build.gradle +++ b/adapters/forge/build.gradle @@ -305,6 +305,8 @@ tasks.named('shadowJar', ShadowJar).configure { // The Bukkit platform binding cannot load without org.bukkit on the classpath, which no mod // loader has. Shipping it only adds dead classes that reference a missing package. exclude('art/arcane/iris/platform/bukkit/**') + exclude('art/arcane/iris/platform/bootstrap/SlimJar*.class') + exclude('art/arcane/iris/platform/bootstrap/NativeRuntimeLibraries*.class') duplicatesStrategy = DuplicatesStrategy.EXCLUDE filesMatching('META-INF/services/**') { FileCopyDetails entry -> entry.duplicatesStrategy = DuplicatesStrategy.INCLUDE diff --git a/adapters/neoforge/build.gradle b/adapters/neoforge/build.gradle index 69a936a0e..f21f4faa1 100644 --- a/adapters/neoforge/build.gradle +++ b/adapters/neoforge/build.gradle @@ -302,6 +302,8 @@ tasks.named('shadowJar', ShadowJar).configure { // The Bukkit platform binding cannot load without org.bukkit on the classpath, which no mod // loader has. Shipping it only adds dead classes that reference a missing package. exclude('art/arcane/iris/platform/bukkit/**') + exclude('art/arcane/iris/platform/bootstrap/SlimJar*.class') + exclude('art/arcane/iris/platform/bootstrap/NativeRuntimeLibraries*.class') duplicatesStrategy = DuplicatesStrategy.EXCLUDE filesMatching('META-INF/services/**') { FileCopyDetails entry -> entry.duplicatesStrategy = DuplicatesStrategy.INCLUDE diff --git a/build.gradle b/build.gradle index a3aedf9a0..433f8ecf4 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,7 @@ import org.gradle.api.file.DeleteSpec import org.gradle.api.file.FileSystemOperations import org.gradle.api.file.FileTree import org.gradle.api.plugins.JavaPlugin +import org.gradle.api.initialization.IncludedBuild import org.gradle.api.tasks.Copy import org.gradle.api.tasks.compile.JavaCompile import org.gradle.jvm.tasks.Jar @@ -78,6 +79,9 @@ String forgeVersion = providers.gradleProperty('forgeVersion').getOrElse('26.3-6 String neoForgeVersion = providers.gradleProperty('neoForgeVersion').getOrElse('26.3.0.33-beta') String volmLibShared = 'com.github.VolmitSoftware.VolmLib:volmlib-shared:master-SNAPSHOT' File localVolmLibCheckout = (File) gradle.extensions.extraProperties.get('volmLibDirectory') +IncludedBuild localVolmLibBuild = localVolmLibCheckout == null ? null : gradle.includedBuilds.find { IncludedBuild includedBuild -> + includedBuild.projectDir.canonicalFile == localVolmLibCheckout.canonicalFile +} Map volmLibStamp = (Map) gradle.extensions.extraProperties.get('volmLibStamp') Closure loaderDisplayVersion = { String loaderVersion -> String coordinatePrefix = "${minecraftVersion}-" @@ -136,6 +140,14 @@ nmsBindings.each { String key -> project(":adapters:bukkit:nms:${key}") { apply plugin: JavaPlugin apply plugin: PaperweightUser + if (localVolmLibBuild != null) { + tasks.named('paperweightUserdevSetup').configure { + dependsOn(localVolmLibBuild.task(":native:${key}:paperweightUserdevSetup")) + if (key == 'v26_2_R1') { + dependsOn(localVolmLibBuild.task(':native-common:paperweightUserdevSetup')) + } + } + } NativeDevelopmentTargets.Target nativeTarget = NativeDevelopmentTargets.require(key) dependencies.extensions.getByType(PaperweightUserDependenciesExtension) .paperDevBundle(nativeTarget.paperBundle()) diff --git a/buildSrc/src/main/java/ModdedArtifactVerifier.java b/buildSrc/src/main/java/ModdedArtifactVerifier.java index f2a10102b..c497bd8e8 100644 --- a/buildSrc/src/main/java/ModdedArtifactVerifier.java +++ b/buildSrc/src/main/java/ModdedArtifactVerifier.java @@ -24,6 +24,10 @@ public final class ModdedArtifactVerifier { "net/jpountz/lz4/LZ4BlockOutputStream" ); private static final String INTERNAL_OSHI_REFERENCE = "oshi/SystemInfo"; + private static final List BUKKIT_DEPENDENCY_BOOTSTRAP_CLASSES = List.of( + "art/arcane/iris/platform/bootstrap/SlimJar", + "art/arcane/iris/platform/bootstrap/NativeRuntimeLibraries" + ); private static final List BUNDLED_RUNTIME_PREFIXES = List.of( "net/jpountz/", "oshi/", @@ -97,6 +101,8 @@ public final class ModdedArtifactVerifier { Set asmEntries = new LinkedHashSet<>(); Set shippedNestedJars = new LinkedHashSet<>(); Set bukkitSupertypeClasses = new LinkedHashSet<>(); + Set dependencyBootstrapViolations = new LinkedHashSet<>(); + Set executableEntries = new LinkedHashSet<>(); boolean oshiReference = false; Enumeration entries = jar.entries(); while (entries.hasMoreElements()) { @@ -106,6 +112,9 @@ public final class ModdedArtifactVerifier { } String name = entry.getName(); + if (name.toLowerCase(Locale.ROOT).endsWith(".exe")) { + executableEntries.add(name); + } if (startsWithAny(name, BUNDLED_RUNTIME_PREFIXES)) { bundledRuntimeEntries.add(name); } @@ -116,7 +125,7 @@ public final class ModdedArtifactVerifier { shippedNestedJars.add(name); } if (isNestedJar(name)) { - inspectNestedJar(jar, entry, bundledRuntimeEntries, privateReferenceClasses); + inspectNestedJar(jar, entry, bundledRuntimeEntries, privateReferenceClasses, dependencyBootstrapViolations, executableEntries); } if (!name.endsWith(".class")) { continue; @@ -124,6 +133,7 @@ public final class ModdedArtifactVerifier { byte[] classFile = readEntryBytes(jar, entry); String bytecode = new String(classFile, StandardCharsets.ISO_8859_1); + inspectDependencyBootstrap(name, name, bytecode, dependencyBootstrapViolations); if (containsAny(bytecode, PRIVATE_REFERENCE_PREFIXES)) { privateReferenceClasses.add(name); } @@ -145,6 +155,13 @@ public final class ModdedArtifactVerifier { } } + if (!executableEntries.isEmpty()) { + throw new GradleException(artifact.getName() + " contains executable .exe entries: " + preview(executableEntries)); + } + if (!dependencyBootstrapViolations.isEmpty()) { + throw new GradleException(artifact.getName() + " contains Bukkit dependency bootstrap code: " + + preview(dependencyBootstrapViolations)); + } if (!bundledRuntimeEntries.isEmpty()) { throw new GradleException(artifact.getName() + " duplicates Minecraft runtime libraries: " + preview(bundledRuntimeEntries)); @@ -364,7 +381,9 @@ public final class ModdedArtifactVerifier { private static void inspectNestedJar(JarFile jar, JarEntry nestedEntry, Set bundledRuntimeEntries, - Set privateReferenceClasses) throws IOException { + Set privateReferenceClasses, + Set dependencyBootstrapViolations, + Set executableEntries) throws IOException { String nestedName = nestedEntry.getName(); if (isNamedRuntimeLibrary(nestedName)) { bundledRuntimeEntries.add(nestedName); @@ -380,11 +399,15 @@ public final class ModdedArtifactVerifier { String name = entry.getName(); String path = nestedName + "!/" + name; + if (name.toLowerCase(Locale.ROOT).endsWith(".exe")) { + executableEntries.add(path); + } if (startsWithAny(name, BUNDLED_RUNTIME_PREFIXES)) { bundledRuntimeEntries.add(path); } if (name.endsWith(".class")) { String bytecode = new String(nestedJar.readAllBytes(), StandardCharsets.ISO_8859_1); + inspectDependencyBootstrap(name, path, bytecode, dependencyBootstrapViolations); if (containsAny(bytecode, PRIVATE_REFERENCE_PREFIXES)) { privateReferenceClasses.add(path); } @@ -393,6 +416,15 @@ public final class ModdedArtifactVerifier { } } + private static void inspectDependencyBootstrap(String entry, String path, String bytecode, Set violations) { + String topLevel = topLevelClass(entry); + for (String forbidden : BUKKIT_DEPENDENCY_BOOTSTRAP_CLASSES) { + if (topLevel.equals(forbidden) || bytecode.contains(forbidden)) { + violations.add(path + " -> " + forbidden); + } + } + } + private static boolean isNestedJar(String name) { return name.endsWith(".jar") && (name.startsWith(NESTED_JAR_DIRECTORY) || name.startsWith("META-INF/jarjar/")); diff --git a/buildSrc/src/test/java/ModdedArtifactVerifierTest.java b/buildSrc/src/test/java/ModdedArtifactVerifierTest.java index efb9303d2..b159b300a 100644 --- a/buildSrc/src/test/java/ModdedArtifactVerifierTest.java +++ b/buildSrc/src/test/java/ModdedArtifactVerifierTest.java @@ -4,6 +4,8 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; import java.io.ByteArrayOutputStream; import java.io.File; @@ -44,6 +46,91 @@ public class ModdedArtifactVerifierTest { ModdedArtifactVerifier.verify(artifact, REQUIRED_ENTRIES); } + @Test + public void rejectsBukkitDependencyBootstrapClassesAndNestedClasses() throws Exception { + for (String internalName : List.of("art/arcane/iris/platform/bootstrap/SlimJar", + "art/arcane/iris/platform/bootstrap/SlimJar$BootstrapLogger", + "art/arcane/iris/platform/bootstrap/NativeRuntimeLibraries", + "art/arcane/iris/platform/bootstrap/NativeRuntimeLibraries$Provider")) { + Map entries = validEntries(); + entries.put(internalName + ".class", classExtending(internalName, "java/lang/Object")); + GradleException failure = assertThrows(GradleException.class, + () -> ModdedArtifactVerifier.verify(createArtifact(entries), REQUIRED_ENTRIES)); + assertTrue(failure.getMessage().contains("Bukkit dependency bootstrap code")); + assertTrue(failure.getMessage().contains(internalName)); + } + } + + @Test + public void rejectsDanglingBukkitDependencyBootstrapReferences() throws Exception { + for (String internalName : List.of("art/arcane/iris/platform/bootstrap/SlimJar", + "art/arcane/iris/platform/bootstrap/NativeRuntimeLibraries")) { + Map entries = validEntries(); + String caller = "art/arcane/iris/modded/BootstrapCaller"; + ClassWriter writer = new ClassWriter(0); + writer.visit(Opcodes.V21, Opcodes.ACC_PUBLIC, caller, null, "java/lang/Object", null); + MethodVisitor method = writer.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, + "dependencyType", "()Ljava/lang/Class;", null, null); + method.visitCode(); + method.visitLdcInsn(Type.getObjectType(internalName)); + method.visitInsn(Opcodes.ARETURN); + method.visitMaxs(1, 0); + method.visitEnd(); + writer.visitEnd(); + entries.put(caller + ".class", writer.toByteArray()); + GradleException failure = assertThrows(GradleException.class, + () -> ModdedArtifactVerifier.verify(createArtifact(entries), REQUIRED_ENTRIES)); + assertTrue(failure.getMessage().contains(caller)); + assertTrue(failure.getMessage().contains(internalName)); + } + } + + @Test + public void rejectsBukkitDependencyBootstrapInsideJarJar() throws Exception { + Map entries = validEntries(); + String internalName = "art/arcane/iris/platform/bootstrap/SlimJar"; + entries.put("META-INF/jarjar/dependency-support.jar", createNestedArtifact(Map.of( + internalName + ".class", classExtending(internalName, "java/lang/Object")))); + GradleException failure = assertThrows(GradleException.class, + () -> ModdedArtifactVerifier.verify(createArtifact(entries), REQUIRED_ENTRIES)); + assertTrue(failure.getMessage().contains("Bukkit dependency bootstrap code")); + assertTrue(failure.getMessage().contains("dependency-support.jar!/" + internalName)); + } + + @Test + public void rejectsExecutableEntriesRegardlessOfCase() throws Exception { + for (String executable : List.of("bin/installer.exe", "native/windows/helper.EXE")) { + Map entries = validEntries(); + entries.put(executable, new byte[]{1}); + GradleException failure = assertThrows(GradleException.class, + () -> ModdedArtifactVerifier.verify(createArtifact(entries), REQUIRED_ENTRIES)); + assertTrue(failure.getMessage().contains("executable .exe entries")); + assertTrue(failure.getMessage().contains(executable)); + } + } + + @Test + public void rejectsExecutableInsideJarJar() throws Exception { + Map entries = validEntries(); + entries.put("META-INF/jarjar/native-support.jar", createNestedArtifact(Map.of( + "native/helper.exe", new byte[]{1}))); + GradleException failure = assertThrows(GradleException.class, + () -> ModdedArtifactVerifier.verify(createArtifact(entries), REQUIRED_ENTRIES)); + assertTrue(failure.getMessage().contains("executable .exe entries")); + assertTrue(failure.getMessage().contains("native-support.jar!/native/helper.exe")); + } + + @Test + public void acceptsBundledCompressionLibraries() throws Exception { + Map entries = validEntries(); + entries.put("native/windows-x86_64/jlibdeflate.dll", new byte[]{1}); + entries.put("native/linux-x86_64/libjlibdeflate.so", new byte[]{1}); + entries.put("native/osx-aarch64/libjlibdeflate.dylib", new byte[]{1}); + entries.put("com/fulcrumgenomics/jlibdeflate/NativeLoader.class", + classExtending("com/fulcrumgenomics/jlibdeflate/NativeLoader", "java/lang/Object")); + ModdedArtifactVerifier.verify(createArtifact(entries), REQUIRED_ENTRIES); + } + @Test public void rejectsClientLanguagesWithoutPackMetadata() throws Exception { Map entries = validEntries();