Way too much

This commit is contained in:
Brian Neumann-Fopiano
2026-08-25 12:52:12 -04:00
parent 2797a5bf22
commit 07cc76614f
131 changed files with 6826 additions and 1094 deletions
@@ -21,9 +21,7 @@ public final class BukkitArtifactVerifier {
// class under one of these is a NoClassDefFoundError waiting for the right code path.
private static final List<String> SHIPPED_PREFIXES = List.of(
"art/arcane/iris/",
"art/arcane/volmlib/",
"com/google/gson/",
"com/googlecode/concurrentlinkedhashmap/"
"art/arcane/volmlib/"
);
// Relocation targets for the libraries slimjar downloads and relocates at runtime. Compiled
// references to them are correct and the classes are correctly absent from the jar.
@@ -38,9 +36,12 @@ public final class BukkitArtifactVerifier {
"art/arcane/iris/util/aether/",
"art/arcane/iris/util/guice/",
"art/arcane/iris/util/dom4j/",
"art/arcane/iris/util/jaxen/"
"art/arcane/iris/util/jaxen/",
"art/arcane/iris/util/gson/",
"art/arcane/iris/util/lru/",
"art/arcane/iris/util/caffeine/",
"art/arcane/iris/util/paralithic/"
);
private static final String CAFFEINE_CACHE_PACKAGE = "art/arcane/iris/util/caffeine/cache/";
private static final String MATTER_SLICE_PACKAGE = "art/arcane/volmlib/util/matter/slices/";
private static final String LANGUAGE_DIRECTORY = "languages/";
@@ -48,10 +49,14 @@ public final class BukkitArtifactVerifier {
}
public static void verify(File artifact, List<String> requiredEntries, int minimumLocales,
int minimumCaffeineFactories, int minimumMatterSlices) {
int minimumMatterSlices, long maximumArtifactBytes) {
if (!artifact.isFile()) {
throw new GradleException("Missing Bukkit Iris artifact: " + artifact.getAbsolutePath());
}
if (artifact.length() > maximumArtifactBytes) {
throw new GradleException(artifact.getName() + " is " + artifact.length()
+ " bytes; Bukkit artifacts must not exceed " + maximumArtifactBytes + " bytes");
}
try (JarFile jar = new JarFile(artifact)) {
for (String requiredEntry : requiredEntries) {
@@ -65,7 +70,6 @@ public final class BukkitArtifactVerifier {
Set<String> shippedClasses = new LinkedHashSet<>();
int locales = 0;
int caffeineFactories = 0;
int matterSlices = 0;
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
@@ -84,9 +88,6 @@ public final class BukkitArtifactVerifier {
String internalName = name.substring(0, name.length() - ".class".length());
shippedClasses.add(internalName);
if (isGeneratedCaffeineFactory(internalName)) {
caffeineFactories++;
}
if (internalName.startsWith(MATTER_SLICE_PACKAGE)) {
matterSlices++;
}
@@ -96,14 +97,6 @@ public final class BukkitArtifactVerifier {
throw new GradleException(artifact.getName() + " ships " + locales + " locale files; expected at least "
+ minimumLocales);
}
// Caffeine picks its cache and node implementation with MethodHandles.Lookup.findClass on a
// name built from the builder's feature flags. Nothing references these statically, so only a
// population check can tell that an exclude or minimize() ate them.
if (caffeineFactories < minimumCaffeineFactories) {
throw new GradleException(artifact.getName() + " ships " + caffeineFactories
+ " generated Caffeine cache classes; expected at least " + minimumCaffeineFactories
+ ". Caffeine resolves these by name and cannot survive static pruning");
}
// Matter.read() resolves slice types from the canonical name stored in the payload.
if (matterSlices < minimumMatterSlices) {
throw new GradleException(artifact.getName() + " ships " + matterSlices
@@ -148,23 +141,6 @@ public final class BukkitArtifactVerifier {
}
}
private static boolean isGeneratedCaffeineFactory(String internalName) {
if (!internalName.startsWith(CAFFEINE_CACHE_PACKAGE)) {
return false;
}
String simpleName = internalName.substring(CAFFEINE_CACHE_PACKAGE.length());
if (simpleName.isEmpty() || simpleName.indexOf('/') >= 0) {
return false;
}
for (int i = 0; i < simpleName.length(); i++) {
if (simpleName.charAt(i) < 'A' || simpleName.charAt(i) > 'Z') {
return false;
}
}
return true;
}
private static byte[] readEntryBytes(JarFile jar, JarEntry entry) throws IOException {
try (InputStream input = jar.getInputStream(entry)) {
return input.readAllBytes();
+97
View File
@@ -0,0 +1,97 @@
import org.gradle.api.GradleException;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public final class JarCompactor {
private static final int BUFFER_BYTES = 64 * 1024;
private JarCompactor() {
}
public static void compact(File artifact) {
if (artifact == null || !artifact.isFile()) {
throw new GradleException("Cannot compact missing jar artifact: " + artifact);
}
Path source = artifact.toPath();
Path temporary = null;
try {
temporary = Files.createTempFile(source.getParent(), artifact.getName(), ".compact");
rewrite(source, temporary);
replace(temporary, source);
} catch (IOException exception) {
if (temporary != null) {
try {
Files.deleteIfExists(temporary);
} catch (IOException cleanupFailure) {
exception.addSuppressed(cleanupFailure);
}
}
throw new GradleException("Unable to compact jar artifact " + artifact.getAbsolutePath(), exception);
}
}
private static void rewrite(Path source, Path destination) throws IOException {
byte[] buffer = new byte[BUFFER_BYTES];
try (InputStream rawInput = new BufferedInputStream(Files.newInputStream(source));
ZipInputStream input = new ZipInputStream(rawInput);
OutputStream rawOutput = new BufferedOutputStream(Files.newOutputStream(destination));
ZipOutputStream output = new ZipOutputStream(rawOutput)) {
output.setLevel(9);
ZipEntry entry;
while ((entry = input.getNextEntry()) != null) {
if (entry.isDirectory()) {
continue;
}
ZipEntry compacted = copyMetadata(entry);
output.putNextEntry(compacted);
int read;
while ((read = input.read(buffer)) >= 0) {
if (read > 0) {
output.write(buffer, 0, read);
}
}
output.closeEntry();
}
}
}
private static ZipEntry copyMetadata(ZipEntry source) {
ZipEntry target = new ZipEntry(source.getName());
target.setMethod(ZipEntry.DEFLATED);
if (source.getTime() >= 0L) {
target.setTime(source.getTime());
}
if (source.getComment() != null) {
target.setComment(source.getComment());
}
if (source.getExtra() != null) {
target.setExtra(source.getExtra());
}
return target;
}
private static void replace(Path temporary, Path source) throws IOException {
try {
Files.move(
temporary,
source,
StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.REPLACE_EXISTING);
} catch (AtomicMoveNotSupportedException exception) {
Files.move(temporary, source, StandardCopyOption.REPLACE_EXISTING);
}
}
}
@@ -30,7 +30,6 @@ public class BukkitArtifactVerifierTest {
NMS_BINDING + ".class"
);
private static final int LOCALES = 2;
private static final int CAFFEINE_FACTORIES = 2;
private static final int MATTER_SLICES = 1;
@Rule
@@ -40,7 +39,7 @@ public class BukkitArtifactVerifierTest {
public void acceptsCompleteArtifact() throws Exception {
File artifact = createArtifact(validEntries());
BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, CAFFEINE_FACTORIES, MATTER_SLICES);
BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, MATTER_SLICES, Long.MAX_VALUE);
}
@Test
@@ -50,8 +49,7 @@ public class BukkitArtifactVerifierTest {
File artifact = createArtifact(entries);
GradleException failure = assertThrows(GradleException.class,
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, CAFFEINE_FACTORIES,
MATTER_SLICES));
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, MATTER_SLICES, Long.MAX_VALUE));
assertTrue(failure.getMessage().contains(PLUGIN_DESCRIPTOR));
}
@@ -62,8 +60,7 @@ public class BukkitArtifactVerifierTest {
File artifact = createArtifact(entries);
GradleException failure = assertThrows(GradleException.class,
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, CAFFEINE_FACTORIES,
MATTER_SLICES));
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, MATTER_SLICES, Long.MAX_VALUE));
assertTrue(failure.getMessage().contains(SLIMJAR_DEPENDENCIES));
}
@@ -74,8 +71,7 @@ public class BukkitArtifactVerifierTest {
File artifact = createArtifact(entries);
GradleException failure = assertThrows(GradleException.class,
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, CAFFEINE_FACTORIES,
MATTER_SLICES));
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, MATTER_SLICES, Long.MAX_VALUE));
assertTrue(failure.getMessage().contains(SLIMJAR_RESOLUTIONS));
}
@@ -86,8 +82,7 @@ public class BukkitArtifactVerifierTest {
File artifact = createArtifact(entries);
GradleException failure = assertThrows(GradleException.class,
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, CAFFEINE_FACTORIES,
MATTER_SLICES));
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, MATTER_SLICES, Long.MAX_VALUE));
assertTrue(failure.getMessage().contains("art/arcane/volmlib/util/noise/CNG"));
}
@@ -96,21 +91,31 @@ public class BukkitArtifactVerifierTest {
Map<String, byte[]> entries = validEntries();
entries.put("art/arcane/iris/Consumer.class",
classReferencing("art/arcane/iris/Consumer", "art/arcane/iris/util/kyori/adventure/text/Component"));
entries.put("art/arcane/iris/GsonConsumer.class",
classReferencing("art/arcane/iris/GsonConsumer", "art/arcane/iris/util/gson/Gson"));
entries.put("art/arcane/iris/LruConsumer.class",
classReferencing("art/arcane/iris/LruConsumer", "art/arcane/iris/util/lru/ConcurrentLinkedHashMap"));
entries.put("art/arcane/iris/CaffeineConsumer.class",
classReferencing("art/arcane/iris/CaffeineConsumer", "art/arcane/iris/util/caffeine/cache/Caffeine"));
entries.put("art/arcane/iris/ParalithicConsumer.class",
classReferencing("art/arcane/iris/ParalithicConsumer", "art/arcane/iris/util/paralithic/functions/Function"));
File artifact = createArtifact(entries);
BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, CAFFEINE_FACTORIES, MATTER_SLICES);
BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, MATTER_SLICES, Long.MAX_VALUE);
}
@Test
public void rejectsStrippedCaffeineFactories() throws Exception {
Map<String, byte[]> entries = validEntries();
entries.remove("art/arcane/iris/util/caffeine/cache/SSMS.class");
File artifact = createArtifact(entries);
public void rejectsArtifactAboveConfiguredSize() throws Exception {
File artifact = createArtifact(validEntries());
GradleException failure = assertThrows(GradleException.class,
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, CAFFEINE_FACTORIES,
MATTER_SLICES));
assertTrue(failure.getMessage().contains("generated Caffeine cache classes"));
() -> BukkitArtifactVerifier.verify(
artifact,
REQUIRED_ENTRIES,
LOCALES,
MATTER_SLICES,
artifact.length() - 1L));
assertTrue(failure.getMessage().contains("must not exceed"));
}
@Test
@@ -120,8 +125,7 @@ public class BukkitArtifactVerifierTest {
File artifact = createArtifact(entries);
GradleException failure = assertThrows(GradleException.class,
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, CAFFEINE_FACTORIES,
MATTER_SLICES));
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, MATTER_SLICES, Long.MAX_VALUE));
assertTrue(failure.getMessage().contains("locale files"));
}
@@ -132,8 +136,7 @@ public class BukkitArtifactVerifierTest {
File artifact = createArtifact(entries);
GradleException failure = assertThrows(GradleException.class,
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, CAFFEINE_FACTORIES,
MATTER_SLICES));
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, MATTER_SLICES, Long.MAX_VALUE));
assertTrue(failure.getMessage().contains("Matter slice types"));
}
@@ -144,7 +147,7 @@ public class BukkitArtifactVerifierTest {
classAnnotatedWith("art/arcane/iris/Annotated", "com/google/errorprone/annotations/CanIgnoreReturnValue"));
File artifact = createArtifact(entries);
BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, CAFFEINE_FACTORIES, MATTER_SLICES);
BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, MATTER_SLICES, Long.MAX_VALUE);
}
private Map<String, byte[]> validEntries() {
@@ -159,10 +162,6 @@ public class BukkitArtifactVerifierTest {
entries.put("art/arcane/volmlib/util/noise/CNG.class", emptyClass("art/arcane/volmlib/util/noise/CNG"));
entries.put("art/arcane/volmlib/util/matter/slices/BlockMatter.class",
emptyClass("art/arcane/volmlib/util/matter/slices/BlockMatter"));
entries.put("art/arcane/iris/util/caffeine/cache/SSMS.class",
emptyClass("art/arcane/iris/util/caffeine/cache/SSMS"));
entries.put("art/arcane/iris/util/caffeine/cache/SSLMS.class",
emptyClass("art/arcane/iris/util/caffeine/cache/SSLMS"));
return entries;
}
@@ -0,0 +1,39 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertNull;
public class JarCompactorTest {
@Rule
public final TemporaryFolder temporaryFolder = new TemporaryFolder();
@Test
public void preservesFilesAndOmitsDirectoryEntries() throws Exception {
File artifact = temporaryFolder.newFile("artifact.jar");
byte[] content = "Iris artifact content".getBytes(StandardCharsets.UTF_8);
try (JarOutputStream output = new JarOutputStream(new FileOutputStream(artifact))) {
output.putNextEntry(new JarEntry("example/"));
output.closeEntry();
output.putNextEntry(new JarEntry("example/value.txt"));
output.write(content);
output.closeEntry();
}
JarCompactor.compact(artifact);
try (JarFile jar = new JarFile(artifact)) {
assertNull(jar.getJarEntry("example/"));
assertArrayEquals(content, jar.getInputStream(
jar.getJarEntry("example/value.txt")).readAllBytes());
}
}
}